ref

在 react 中并不建议使用 ref 属性, 而应该尽量使用状态提升, 但是 react 还是提供了 ref 属性赋予了开发者操作 dom 的能力, react 的 ref 有 stringcallbackcreateRef 三种形式, 分别如下:

// 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 使用场景

React.forwardRef 后面跟一个 render 函数。用法如下:

function HOCComponent(WrapComponent) {
return class extends React.Component {
render() {
const { ref, ...rest } = this.props
return <WrapComponent ref={ref} { ...rest } />
}
}
}
// 此时的 ref 指向的是, 高阶组件里包裹的 WrapComponent 组件。
const Demo = React.forwardRef((props, ref) => {
return <HOCComponent { ...props } ref={ref} />
})

使用场景: 需要引用高阶组件里的子组件节点的时候可以使用 React.forwordRef