在 react 中并不建议使用 ref 属性, 而应该尽量使用状态提升, 但是 react 还是提供了 ref 属性赋予了开发者操作 dom 的能力, react 的 ref 有 string
、callback
、createRef
三种形式, 分别如下:
// string 这种写法未来会被抛弃class MyComponent extends Component {componentDidMount() {this.refs.myRef.focus()}render() {return <input ref="myRef" />}}// callback(比较通用)class MyComponent extends Component {componentDidMount() {this.myRef.focus()}render() {return <input ref={(ele) => {this.myRef = ele}} />}}// react 16.3 增加, 其它 react-like 框架还没有同步class MyComponent extends Component {constructor(props) {super(props)this.myRef = React.createRef()}componentDidMount() {this.myRef.current.focus()}render() {return <input ref={this.myRef} />}}
React ref 的前世今生 罗列了三种写法的差异, 下面对上述例子中的第二种写法(比较通用)进行实现。
首先在 setAttribute 方法内补充上对 ref 的属性进行特殊处理,
function setAttribute(dom, attr, value) {...else if (attr === 'ref') { // 处理 ref 属性if (_.isFunction(value)) {value(dom)}}...}
针对这个例子中 this.myRef.focus()
的 focus 属性需要异步处理, 因为调用 componentDidMount 的时候, 界面上还未添加 dom 元素。处理 renderComponent 函数:
function renderComponent(component) {...else if (component && component.componentDidMount) {defer(component.componentDidMount.bind(component))}...}
刷新页面, 可以发现 input 框已为选中状态。
处理完普通元素的 ref 后, 再来处理下自定义组件的 ref 的情况。之前默认自定义组件上是没属性的, 现在只要针对自定义组件的 ref 属性做相应处理即可。稍微修改 vdomToDom 函数如下:
function vdomToDom(vdom) {if (_.isFunction(vdom.nodeName)) { // 此时是自定义组件...for (const attr in vdom.attributes) { // 处理自定义组件的 ref 属性if (attr === 'ref' && _.isFunction(vdom.attributes[attr])) {vdom.attributes[attr](component)}}...}...}
跑如下测试用例:
class A extends Component {constructor() {super()this.state = {count: 0}this.click = this.click.bind(this)}click() {this.setState({count: ++this.state.count})}render() {return <div>{this.state.count}</div>}}class B extends Component {constructor() {super()this.click = this.click.bind(this)}click() {this.A.click()}render() {return (<div><button onClick={this.click}>加1</button><A ref={(e) => { this.A = e }} /></div>)}}
效果如下:
React.forwardRef
后面跟一个 render
函数。用法如下:
function HOCComponent(WrapComponent) {return class extends React.Component {render() {const { ref, ...rest } = this.propsreturn <WrapComponent ref={ref} { ...rest } />}}}// 此时的 ref 指向的是, 高阶组件里包裹的 WrapComponent 组件。const Demo = React.forwardRef((props, ref) => {return <HOCComponent { ...props } ref={ref} />})
使用场景: 需要引用高阶组件里的子组件节点的时候可以使用 React.forwordRef