React使用枚举式条件渲染时意外渲染未用到组件的原因
问题背景
你原本想用简洁的对象枚举方式来根据条件渲染AComponent或BComponent,代码大概是这样:
render() { const displayMode = this.props.condition ? 'a' : 'b'; return ( <div> {{ a: <AComponent />, b: <BComponent userData={this.props.userData} /> }[displayMode]} </div> ); }
结果发现,当渲染AComponent时(此时userData不可用),居然会触发BComponent的props缺失报错——明明没渲染它,怎么会报错呢?换成&&条件判断的写法就正常了:
render() { const displayMode = this.props.condition ? 'a' : 'b'; return ( <div> {displayMode === 'a' && <AComponent />} {displayMode === 'b' && <BComponent userData={this.props.userData} />} </div> ); }
你想知道这个异常行为的原因,以及它是否属于预期情况。
原因拆解
其实这个问题和React关系不大,核心是JavaScript对象字面量的求值规则:
当你写出{ a: <AComponent />, b: <BComponent userData={...} /> }这个对象时,对象里的所有属性值都会被立即计算——也就是说,不管你最后会不会用到b对应的属性,React.createElement(BComponent, { userData: this.props.userData })都会被执行一遍。
React的JSX本质就是React.createElement的语法糖,所以这一步已经在内存中创建了BComponent的元素对象,同时会校验它的props是否符合要求。这时候如果userData不存在,自然就会抛出报错,哪怕你最后根本没把这个组件渲染到DOM里。
而&&条件判断的逻辑是短路求值:只有当displayMode === 'b'为真时,后面的<BComponent ... />才会被求值(也就是调用React.createElement)。如果条件不满足,这部分代码根本不会执行,自然不会触发props校验。
这是预期行为吗?
完全是预期行为!这不是React的bug,而是JavaScript的基础特性导致的。对象字面量的属性初始化总是会全部执行,不管后续是否会访问这些属性。React只是忠实地执行了你写的代码而已。
保留枚举写法的解决方案
如果你偏爱枚举式的简洁,又不想提前触发未使用组件的props校验,可以把组件改成懒加载的函数形式,只有当你调用函数时才会创建组件元素:
render() { const displayMode = this.props.condition ? 'a' : 'b'; const componentMap = { a: () => <AComponent />, b: () => <BComponent userData={this.props.userData} /> }; return ( <div> {componentMap[displayMode]()} </div> ); }
这样只有当你调用对应的函数时,才会执行JSX的转换和props校验,完美解决了提前求值的问题,同时保留了枚举写法的简洁性。
内容的提问来源于stack exchange,提问作者Dygerati

