在介绍 Refs 以前,咱们先来了解两个概念:受控组件 和 不受控组件。react
在HTML中,表单元素(如 input、textarea、select)之类的表单元素一般能够本身维护state,并根据用户的输入进行更新。而在React中,可变状态(mutable state)一般保存在组件的 state 属性中,而且只能经过 setState()来更新。 在此,咱们将 React的state做为惟一的数据源,经过渲染表单的React组件来控制用户输入过程当中表单发送的操做。 这个“被React经过此种方式控制取值的表单输入元素”被成为受控组件。react-native
从字面意思来理解:不被React组件控制的组件。在受控制组件中,表单数据由 React组件处理。其替代方案是不受控制组件,其中表单数据由DOM自己处理。文件输入标签就是一个典型的不受控制组件,它的值只能由用户设置,经过DOM自身提供的一些特性来获取。数组
受控组件和不受控组件最大的区别就是前者自身维护的状态值变化,能够配合自身的change事件,很容易进行修改或者校验用户的输入。微信
在React中 由于 Refs的出现使得 不受控制组件自身状态值的维护变得容易了许多,接下来咱们就重点介绍一下 Refs的使用方式。app
Refs 是一个 获取 DOM节点或 React元素实例的工具。在 React 中 Refs 提供了一种方式,容许用户访问DOM 节点或者在render方法中建立的React元素。ide
在 React单项数据流中,props是父子组件交互的惟一方式。要修改一个子组件,须要经过的新的props来从新渲染。 可是在某些状况下,须要在数据流以外强制修改子组件。被修改的子组件多是一个React组件实例,也多是一个DOM元素。对于这两种状况,React 都经过 Refs的使用提供了具体的解决方案。函数
refs 一般适合在一下场景中使用:工具
避免使用 refs 去作任何能够经过声明式实现来完成的事情。例如,避免在Dialog、Loading、Alert等组件内部暴露 open(), show(), hide(),close()等方法,最好经过 isXX属性的方式来控制。动画
关于refs的使用有两种方式: 1)经过 React.createRef() API【在React 16.3版本以后引入了】;2)在较早的版本中,咱们推荐使用 回调形式的refs。ui
class TestComp extends React.Component {
constructor(props) {
super(props);
this.tRef = React.createRef();
}
render() {
return (
<div ref={ this.tRef }></div>
)
}
}
复制代码
以上代码 建立了一个实例属性 this.tRef, 并将其 传递给 DOM元素 div。后续对该节点的引用就能够在ref的 current属性中访问。ref的值根据节点类型的不一样结果也不一样:
class TestComp extends React.Component {
constructor(props) {
super(props);
// 建立一个 ref 来存储 DOM元素 input
this.textInput = React.createRef();
this.focusEvent = this.focusEvent.bind(this);
}
focusEvent() {
// 直接经过原生API访问输入框获取焦点事件
this.textInput.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.textInput} />
<input type="button" value="获取文本框焦点事件" onClick={this.focusEvent}/>
</div>
);
}
}
复制代码
class ParentComp extends React.Component {
constructor(props) {
super(props);
// 建立ref 指向 ChildrenComp 组件实例
this.textInput = React.createRef();
}
componentDidMount() {
// 调用子组件 focusTextInput方法 触发子组件内部 文本框获取焦点事件
this.textInput.current.focusTextInput();
}
render() {
return (
<ChildrenComp ref={ this.textInput } /> ); } } 复制代码
class ChildrenComp extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
focusTextInput() {
this.inputRef.current.focus();
}
render(){
return(
<div> <input type='text' value='父组件经过focusTextInput()方法控制获取焦点事件' ref={ this.inputRef }/> </div> ) } } 复制代码
React 也支持另一种使用 refs的方式成为 “回调 refs”,能够帮助咱们更精准的控制什么时候 refs被设置和解除。 这个回调函数中接受 React 组件实例或 HTML DOM 元素做为参数,以使它们能在其余地方被存储和访问。
class TestComp extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
// 使用'ref'的回调函数将 text输入框DOM节点的引用绑定到 React实例 this.textInput上
this.inputRef = element => {
this.textInput = element;
}
this.focus = () => {
if (this.textInput) {
this.textInput.focus();
}
}
}
componentDidMount() {
this.focus();
}
render() {
return (
<div> <input type='text' ref={ this.inputRef } /> </div> ); } } 复制代码
React 将在组件挂载时会调用 ref 回调函数并传入DOM 元素,当卸载时调用它并传入 null。 在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 必定是最新的。 在类组件中,一般父组件 把它的refs回调函数 经过props的形式传递给子组件,同时子组件把相同的函数做为特殊的 ref属性 传递给对应的 DOM 元素。
若是 ref 回调函数是之内联函数的方式定义的,在更新过程当中它会被执行两次,第一次传入参数 null,而后第二次会传入参数 DOM 元素。这是由于在每次渲染时会建立一个新的函数实例,因此 React 清空旧的 ref 而且设置新的。经过将 ref 的回调函数定义成 class 的绑定函数的方式能够避免上述问题,可是大多数状况下它是可有可无的。
class TestComp extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
// 初始化 flag 值为 init
this.state = {
flag: 'init'
}
this.focus = () => {
if (this.textInput) {
this.textInput.focus();
}
}
}
componentDidMount() {
this.focus();
// 当执行完 render 首次渲染以后,更新状态 flag 值 为 update
this.setState({
flag: 'update'
});
}
render() {
return (
<div> {/* 经过内联回调形式定义 ref */} <input type='text' value={this.state.flag} ref={(element) => { console.log('element', element); // 将传入的 element 输出控制台 this.textInput = element; }} /> </div> ) } } 复制代码
若是你目前还在使用 this.refs.textInput 这种方式访问refs,官方建议使用 回调函数 或者 createRef API的方式来替换。
在极少数状况下,咱们可能但愿在父组件中引用子节点的 DOM 节点(官方不建议这样操做,由于它会打破组件的封装),用户触发焦点或者测量子DOM 节点的大小或者位置。虽然咱们能够经过向子组件添加 ref的方式来解决,但这并非一个理想的解决方案,由于咱们只能获取组件实例而不是 DOM节点。而且它还在函数组件上无效。
在react 16.3 或者更高版本中,咱们推荐使用 ref 转发的方式来实现以上操做。
ref 转发使得组件能够像暴露本身的 ref同样暴露子组件的 ref。
Ref forwarding is a technique for automatically passing a ref through a component to one of its children. This is typically not necessary for most components in the application. However, it can be useful for some kinds of components, especially in reusable component libraries.
Ref forwarding 是一种自动将ref 经过组件传递给其子节点的技术。下面咱们经过具体的案例来演示一下效果。
const ref = React.createRef();
const BtnComp = React.forwardRef((props, ref) => {
return (
<div> <button ref={ref} className='btn'> { props.children } </button> </div>
)
});
class TestComp extends React.Component {
clickEvent() {
if (ref && ref.current) {
ref.current.addEventListener('click', () => {
console.log('hello click!')
});
}
}
componentDidMount() {
console.log('当前按钮的class为:', ref.current.className); // btn
this.clickEvent(); // hello click!
}
render() {
return (
<div> <BtnComp ref={ref}>点击我</BtnComp> </div>
);
}
}
复制代码
上述案例,使用的组件BtnComp 能够获取对底层 button DOM 节点的引用并在必要时对其进行操做,就像正常的HTML元素 button直接使用DOM同样。
第二个ref参数仅在使用React.forwardRef 回调 定义组件时存在。常规函数或类组件不接收ref参数,而且在props中也不提供ref。
Ref转发不只限于DOM组件。您也能够将refs转发给类组件实例。
高阶组件(HOC)是React中用于重用组件逻辑的高级技术,高阶组件是一个获取组件并返回新组件的函数。下面咱们经过具体的案例来看一下refs如何在高阶组件钟正常使用。
// 记录状态值变动操做
function logProps(Comp) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const { forwardedRef, ...rest } = this.props;
return <Comp ref={ forwardedRef } {...rest} />;
}
}
return React.forwardRef((props, ref) => {
return <LogProps { ...props } forwardedRef={ ref } />;
});
}
// 子组件
const BtnComp = React.forwardRef((props, ref) => {
return (
<div>
<button ref={ref} className='btn'>
{ props.children }
</button>
</div>
)
});
// 被logProps包装后返回的新子组件
const NewBtnComp = logProps(BtnComp);
class TestComp extends React.Component {
constructor(props) {
super(props);
this.btnRef = React.createRef();
this.state = {
value: '初始化'
}
}
componentDidMount() {
console.log('ref', this.btnRef);
console.log('ref', this.btnRef.current.className);
this.btnRef.current.classList.add('cancel'); // 给BtnComp中的button添加一个class
this.btnRef.current.focus(); // focus到button元素上
setTimeout(() => {
this.setState({
value: '更新'
});
}, 10000);
}
render() {
return (
<NewBtnComp ref={this.btnRef}>{this.state.value}</NewBtnComp>
);
}
}
复制代码
最终的效果图以下:
注明:文章来源于公众号 react_native, 已通过做者受权转载。