函数组件与类组件在写法没有好坏之分,性能差距也几乎可以忽略,而且 React 会长期支持这两种写法。
React的函数式组件和类组件之间的根本区别 在心智模型上。
- 函数式组件具有
capture value
特性。
capture value特性
Capture Value 从字面上可以理解为固化的值。
可以认为每次 Render 的内容都会形成一个快照并保留下来,因此当状态变更而 Rerender 时,就形成了 N 个 Render 状态,而 每个 Render 状态都拥有自己固定不变
的 Props
与 State
。
也就是说在当前这个render中, state与props可以看作是 常量
!
举例说明-props
类组件
现在存在 类式子组件Classson, 父组件将count作为属性传递到该子组件中
- 父组件
function Fa (){ const [count, setCount] = React.useState(5) return( <div> {count} <br /> <button onClick={()=>{setCount(precount => precount+1)}}> 点我修改count </button> <br /> <Classson count={ count }/> </div> ) } ReactDOM.render(<Fa />, document.getElementById('test'))
- Classson子组件
class Classson extends React.Component{ getCountValue = ()=>{ setTimeout(()=>{ console.log('@@@class', this.props.count) },3000) } render(){ return( <button onClick={this.getCountValue}> class - 点我显示props.count值</button> ) } }
- 页面展示
- 执行步骤
- [1]点击 “ class - 点我显示props.count值” 按钮 -> 在子组件打印通过父组件传递过去的count值;
- [2]在3s内(子组件打印是延迟3s 的)通过 “ 点我修改count ” -> 在父组件修改count值 -> 由5变为6;
- 等待定时器结束 -> 在控制台查看子组件打印的值
- 执行结果
- 控制台打印 -> @@@class 6
- 打印的是最新数据
- 原因 -> React 文档中描述的
props 不是不可变数据
吗?为什么类组件可以展示修改后的数据呢?- 虽然props是不可变的,但是 在class组件中
this是可变的
,react在更新过程中会修改this上的数据,保证你能够在 render和其他的生命周期方法里,读取到最新的数据(props, state),因此this.props 的调用会导致每次都访问最新的 props
;
若是如上代码,打印的值就是5了,因为在render之前就将count值获取并赋值给变量了,之后的使用就和props的更新没有关系了class Classson extends React.Component{ getCountValue = ()=>{ const {count} = this.props setTimeout(()=>{ console.log('@@@class', count) },3000) } render(){ return( <button onClick={this.getCountValue}> class - 点我显示props.count值</button> ) } }
- 虽然props是不可变的,但是 在class组件中
函数组件
现在存在 函数式子组件Classson, 父组件将count作为属性传递到该子组件中
-
父组件
function Fa (){ const [count, setCount] = React.useState(5) return( <div> {count} <br /> <button onClick={()=>{setCount(precount => precount+1)}}> 点我修改count </button> <br /> <Functionson count={ count }/> </div> ) } ReactDOM.render(<Fa />, document.getElementById('test'))
-
函数式组件
function Functionson (props){ const getCountValue = ()=>{ setTimeout(()=>{ console.log('@@@function', props.count) },3000) } return ( <button onClick={getCountValue}> function - 点我显示props.count值</button> ) }
-
页面展示
-
执行步骤
- [1]点击 “ function - 点我显示props.count值 ” 按钮 -> 在子组件打印通过父组件传递过去的count值;
- [2]在3s内通过 “ 点我修改count ” -> 在父组件修改count值 -> 由5变为6;
- 等待定时器结束 -> 在控制台查看子组件打印的值
-
执行结果
- 控制台打印 -> @@@function 5
- 打印的是之前render的数据
-
原因 -> React 文档中描述的
props 是不可变数据
- 函数组件中不存在this,因此在函数组件中
props是不可变
的; - 因此在每次render中 props就可以看作一个常量
- 逻辑如下
挂载组件时,count值为5 ,此时生成了一个快照,当我点击按钮时,此时放入异步队列的想当于
而不是setTimeout(()=>{ console.log('@@@function', 5) },3000)
因此当在父组件修改count值,造成子组件rerender时,不会影响上一次的render的数据,而是重新形成一个新的快找,在这个render中count值为6setTimeout(()=>{ console.log('@@@function', props.count) },3000)
- 函数组件中不存在this,因此在函数组件中
总结
- 总结 -> 对于props的值
- 类组件 展示的是
修改后
的值; - 函数组件 展示的是
修改前
的值;
- 类组件 展示的是
举例说明-state
使用hook useState同样存在capture value特性
- 需求: 实现一个按钮默认显示 false,点击后立即更改为 true,两秒后变回 false
- 类组件
点击 “点击修改状态” 按钮之后, 页面文本会变为 “显示”, 2s后会显示“隐藏”。class Mycomponent extends React.Component{ state={flag:false} editFlag = ()=>{ this.setState((state)=>({flag: !state.flag}),()=>{ console.log(this.state) }) setTimeout(()=>{ this.setState((state)=>({flag: !state.flag})) },2000) } render(){ const { flag } = this.state return ( <div> <span>{ flag ? '显示' : '隐藏'}</span> <button onClick={this.editFlag}>点击修改状态</button> </div> ) } }
- 函数组件
修改我们可以将修改方法的第一个参数传入回调函数,接收值就是上一次的值function Mycomponent (){ const [flag ,setFlag ] = React.useState(false) let timer const editFlag = () => { setFlag(!flag) timer = setTimeout(()=>{ setFlag(preState =>(!preState)) },2000) } React.useEffect(()=>{ return ( clearInterval(timer) ) }) return ( <div> <span>{ flag ? '显示' : '隐藏'}</span> <button onClick={editFlag}>点击修改状态</button> </div> ) }
但是若是我们在修改后去获取这个值呢setFlag(preState =>(!preState))
原因是初始化时,生成的 快照中, flag为false,上述代码相当于如下function Mycomponent (){ const [flag ,setFlag ] = React.useState(false) let timer const editFlag = () => { setFlag(!flag) timer = setTimeout(()=>{ console.log('状态', flag) // false而不是true },2000) } React.useEffect(()=>{ return ( clearInterval(timer) ) }) return ( <div> <span>{ flag ? '显示' : '隐藏'}</span> <button onClick={editFlag}>点击修改状态</button> </div> ) }
当 setFlag(!false) 结束 去rerender时已经 属于下一次render了!const editFlag = () => { setFlag(!false) timer = setTimeout(()=>{ console.log('状态', false) // false而不是true },2000) }
参考文档
- 函数组件与类式组件的区别
- Capture Value 特性
- 函数组件与类式组件的区别