Open jtwang7 opened 2 years ago
项目中引入了两个 ECharts 图表,由于两个图标逻辑样式等类似,因此写成了一个公用的函数组件,通过传入不同的 props 来制定出不同的 ECharts 图表。按理来说,两个图表之间的状态是互不影响的 (React 本身就是这么设计的,每个函数组件都有自己独立的作用域),但是,在传入不同 props 的时候发现:始终渲染的是后一个创建的图表 ?!
起初认为是因为没有设置 key 值,导致 React 内部优化实现了组件的复用,但添加 key 值后并没有什么改变,React key 只在数组上下文中生效。 后来想到,React 组件内部的状态是独立的,这一点毋庸置疑,相同组件的复用,其状态在一般情况下是互不影响的,因为它由组件实例(函数组件为例)的作用域持有。除非!你把这个状态变量定义在了函数组件外部!React 在每次 props 状态改变的时候,都会重新执行(函数)组件的生成函数,若状态被定义在外部,那么复用的组件在执行生成函数时,会因为闭包导致持有外部的状态变量(上一个组件的部分状态),从而出错。
import ... let myChart = null; // 错误的做法,组件复用时,TimerCommonBar 函数会持有这个闭包变量 export default function TimerCommonBar(props) { const ref = useRef(null); // init echarts useEffect(() => { myChart= echarts.init(ref.current); myChart.setOption(option) return () => { myChart.dispose(); } }, []) useEffect(() => { option.series[0].data = data myChart.setOption(option) }, [data]) return ( <div ref={ref} className={`timer-line-${type}bar`}></div> ) }
假设用上述代码创建了两个组件,创建第一个组件时 myChart = null,创建完成后 myChart = 1;创建第二个组件时 myChart = 1,创建完成后 myChart = 2;然后我们修改第一个组件,由于实例已经存在,所以不会再改变 myChart,但是此时 myChart = 2,导致我们以为修改的是第一个组件,实际改变了第二个组件。
myChart = null
myChart = 1
myChart = 2
函数组件的外部变量被复用了,没有相互独立。
import ... export default function TimerCommonBar(props) { const ref = useRef(null); const myChart = useRef(null); // 正确做法,将状态以 React 规范存储在函数内部,React 会在重复调用组件生成函数时,独立持有这一状态. // init echarts useEffect(() => { myChart.current = echarts.init(ref.current); myChart.current.setOption(option) return () => { myChart.current.dispose(); } }, []) useEffect(() => { option.series[0].data = data myChart.current.setOption(option) }, [data]) return ( <div ref={ref} className={`timer-line-${type}bar`}></div> ) }
React 函数组件利用了函数作用域的性质,使得它内部的状态变量会互相隔离,不会被影响。但是定义在函数组件外部的变量没有被隔离,导致函数组件复用时状态混乱。 出于安全和代码可维护性考虑,函数组件外部最好不要定义任何'变量'(这里指的是let和var定义 并且后续会修改改变量), 只应该使用 import 导入,或者 const 定义常量。 我们之所以在有些情况下将变量定义到函数组件外部,是因为每次更新都会重新执行函数组件,导致函数组件内一些永不改变的变量被反复的创建,影响性能。不是说变量定义到函数组件外部是错误的,我们应该遵循以下原则:
若永不改变的变量创建开销极小,也可以定义在函数组件内部,且不需要记忆化处理。
问题场景描述
项目中引入了两个 ECharts 图表,由于两个图标逻辑样式等类似,因此写成了一个公用的函数组件,通过传入不同的 props 来制定出不同的 ECharts 图表。按理来说,两个图表之间的状态是互不影响的 (React 本身就是这么设计的,每个函数组件都有自己独立的作用域),但是,在传入不同 props 的时候发现:始终渲染的是后一个创建的图表 ?!
原因
起初认为是因为没有设置 key 值,导致 React 内部优化实现了组件的复用,但添加 key 值后并没有什么改变,React key 只在数组上下文中生效。 后来想到,React 组件内部的状态是独立的,这一点毋庸置疑,相同组件的复用,其状态在一般情况下是互不影响的,因为它由组件实例(函数组件为例)的作用域持有。除非!你把这个状态变量定义在了函数组件外部!React 在每次 props 状态改变的时候,都会重新执行(函数)组件的生成函数,若状态被定义在外部,那么复用的组件在执行生成函数时,会因为闭包导致持有外部的状态变量(上一个组件的部分状态),从而出错。
代码实例
错误代码
假设用上述代码创建了两个组件,创建第一个组件时
myChart = null
,创建完成后myChart = 1
;创建第二个组件时myChart = 1
,创建完成后myChart = 2
;然后我们修改第一个组件,由于实例已经存在,所以不会再改变 myChart,但是此时myChart = 2
,导致我们以为修改的是第一个组件,实际改变了第二个组件。正确代码
总结
React 函数组件利用了函数作用域的性质,使得它内部的状态变量会互相隔离,不会被影响。但是定义在函数组件外部的变量没有被隔离,导致函数组件复用时状态混乱。 出于安全和代码可维护性考虑,函数组件外部最好不要定义任何'变量'(这里指的是let和var定义 并且后续会修改改变量), 只应该使用 import 导入,或者 const 定义常量。 我们之所以在有些情况下将变量定义到函数组件外部,是因为每次更新都会重新执行函数组件,导致函数组件内一些永不改变的变量被反复的创建,影响性能。不是说变量定义到函数组件外部是错误的,我们应该遵循以下原则: