本文作者:IMWeb eden 原文出处:IMWeb社区 未经同意,禁止转载
在React组件中,props是父组件与子组件的唯一通信方式,但是在某些情况下我们需要在props之外强制修改子组件或DOM元素,这种情况下React提供了Refs解决
下面列举几个场景:
注意:如果能使用props实现,应该尽量避免使用refs实现
下面分别介绍每一种方式
class List extends React.Component {
constructor(props, context) {
super(props, context);
}
componentDidMount(){
this.refs.inputEl.focus();
}
render() {
const { list } = this.props;
return (
<input ref="inputEl"/>
);
}
}
class Child extends React.Component {
render() {
const { renderer } = this.props;
return <div>{renderer(1)}</div>;
}
}
class App extends React.Component {
render() {
return (
<div className="App">
<Child renderer={index => <div ref="test">{index}</div>} />
</div>
);
}
}
上面这种情况,会导致test绑定的实例时Child上面,并不是App上
相比 字符串模式 更加灵活,也避免了诸多问题
class List extends React.Component {
constructor(props, context) {
super(props, context);
}
_ref = el => {
if (el) {
if (!this.els) {
this.els = [];
}
this.els.push(el);
} else {
this.els = [];
}
};
render() {
const { list } = this.props;
return (
<ul>
{list.map((item, index) => {
return (
<li ref={this._ref} key={index}>
{item}
</li>
);
})}
</ul>
);
}
}
class App extends React.Component {
state = {
value: "",
list: []
};
onChange = ({ target: { value } }) => {
this.setState({ value });
};
add = () => {
const { list, value } = this.state;
list.push(value);
this.setState({
value: "",
list
});
};
render() {
const { value, list } = this.state;
return (
<div className="App">
<input value={value} onChange={this.onChange} />
<button onClick={this.add}>add</button>
<List list={list} />
</div>
);
}
}
function Input(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}
class InputBox extends React.Component {
_ref = (el)=>{
this.inputElement = el
}
render() {
return (
<Input
inputRef={this._ref}
/>
);
}
}
通常为了绑定一个组件(元素)实例到当前实例上需要写一个函数,代码结构上看起来很冗余,为了一个变量,使用一个函数去绑定,每一个绑定组件(元素)都需要一个方法处理,大材小用
因此React 提供了更简单有效的解决方案React.createRef()
使用 React.createRef() 创建 refs,通过 ref 属性来获得 React 元素。当构造组件时,refs 通常被赋值给实例的一个属性,这样你可以在组件中任意一处使用它们.
class Test extends React.Component{
myRef = React.createRef();
componentDidMount(){
// 访问ref
const dom = this.myRef.current
}
render(){
return (
<div ref={this.myRef}/>
)
}
}
试试不是比创建一个回调函数更加简洁了?
ref的值取决于节点的类型:
class Sub extends Component{
render(){
const {forwardRef} = this.props;
return <div ref={forwardRef}/>
}
}
class Sup extends Component{
subRef = React.createRef();
render(){
return <Sub forwardRef={this.subRef}/>
}
}
将父组件ref作为一个props传入,在子组件显示调用
class Sub extends Component{
render(){
const {forwardRef} = this.props;
return <div ref={forwardRef}/>
}
}
function forwardRef(props, ref){
return <Sup {...props} forwardRef={ref}/>
}
// 为了devtool中展示有意义的组件名称
forwardRef.displayName=`forwardRef-${Component.displayName||Component.name}`
const XSub = React.forwardRef(forwardRef);
class Sup extends Component{
_ref=(el)=>{this.subEl =el};
render(){
return <XSub ref={this._ref}/>
}
}
React.forwardRef方式,对于使用组件者来说,ref是透明的,不需要额外定一个props传入,直接传递到了下级组件,作为高阶组件封装时,这样做更加友好.
首先我们看看String Refs的实现 String refs 在组件挂载、更新之前会被替换为一个函数如下图,因此实际上,String refs只是 函数是Refs的一种特殊场景
Ref在何时被赋值?
在源码中有两个方法commitAttachRef
挂载实例,commitDetachRef
卸载实例。commitAttachRef
当组件渲染完毕(componentDidMount/comonentDidUpdate)后被执行; commitDetachRef
则在组件或元素被销毁前执行(componentWillUnmount之前),清理引用
挂载: 如果是方法直接执行并传入实例,否则就是采用createRef创建的对象,作为挂在点
卸载:方法被传入
null
值,createRef方式就将current赋值null
,因此我们在使用函数模式时要注意传入null时需要清理引用,有的场景我们会将多个实例绑定到一个同一个对象或数组上。
Refs 字符串模式已经废弃,React 不建议使用并且会提示警告,开发中推荐使用React.forwardRef方式,简单优雅,回调函数模式应用在复杂场景中。