静态组件和动态组件 静态组件 React中的函数组件是无状态的,当在函数组件内部定义一个变量,并尝试在事件处理函数中修改这个变量时,确实,组件内部的变量值会改变,但这个改变不会触发组件的重新渲染,因为它不是一个状态变量。React依赖状态(state)和属性(props)的改变来决定是否需要重新渲染组件。如果这些值不变,React就假定组件输出不需要更新。
举个例子,如果你这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import React from "react" ;import PropTypes from 'prop-types' ;const ClassCom = ({sumA,sumB} ) => { let count = 0 ; return ( <div > <div > <div > sumA: {sumA}</div > <div > sumB: {sumB}</div > <div > count: {count}</div > </div > <div > <button onClick ={() => { sumA++; console.log('sumA', sumA) }}>sumA增加</button > <button onClick ={() => { sumB++; console.log('sumB', sumB) }}>sumB增加</button > <button onClick ={() => { count = sumA + sumB; console.log('count', count) }}>count = sumA + sumB</button > </div > </div > ) } ClassCom .propTypes = { sumA : PropTypes .number .isRequired , sumB : PropTypes .number .isRequired } export default ClassCom ;
在上面的代码中,点击按钮会增加sumA、sumB、count
的值,但是不会触发组件的重新渲染。因为sumA、sumB、count
不是一个状态变量,所以React不会关注它的变化,因此变量虽然变化,但是视图不会更新。
动态组件 动态组件是指那些根据用户交互、数据请求或任何其他类型的异步事件来改变其行为和/或外观的组件。在React中,这通常意味着组件的状态(state)会随时间发生变化,从而导致UI的更新。动态组件能够响应用户输入,如点击、滚动、键盘输入等,或者根据从外部获取的数据来改变展示内容。
类组件并不默认就是动态组件,它们成为动态的是因为它们的状态(state)可以被改变。类组件通过内部状态管理(使用this.state
和this.setState
)来实现动态行为。当内部状态发生变化时,React会重新渲染组件,从而反映状态的最新值。
函数组件在引入Hooks之前,通常被视为无状态组件,只能接受props并渲染它们。但现在,函数组件可以使用useState
这样的Hook来引入和管理内部状态,以及使用useEffect
和其他Hooks来处理副作用和订阅模式。useState
返回一个状态变量和一个更新这个状态变量的函数,使得函数组件能够像类组件一样处理动态行为。
动态组件是由于实现了状态管理和响应生命周期事件的能力。通过这些机制,它们能够根据外部和内部的变化来更新UI。
函数组件+Hooks组合我们后边另开篇幅详细讲解 ,下边先了解一下React中的类组件。
ES6Class类基础知识复习 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Parent { static name = 'parent' ; constructor ( ) { this .age = 18 ; } sex = 'man' ; getAge = () => { return this .age ; } getName ( ) { return this .name ; } } Parent .prototype .school = 'xxx' ;const p = new Parent ();
console.log(p)
查看类实例的内部属性和方法
使用类需要先实例化
1 2 const p = new Parent ();console .log (p.getAge );
通过static
关键字添加的静态属性和方法需要通过console.dir(Parent)
查看
使用类的公共方法
1 console .log (Parent .getName )
extends关键字继承
1 2 3 4 5 class Parent extends React.Component { } const p = new Parent ();console .log (p);
1、首先基于call继承
1 2 3 4 5 6 7 8 9 10 11 12 function Component (props, context, updater ) { this .props = props; this .context = context; this .refs = emptyObject; this .updater = updater || ReactNoopUpdateQueue ; } 为创建的实例p设置四个私有属性:props、context、refs、undater
2、再基于原型继承
继承层次如下:
实例
-> Parent.Prototype
-> React.Component.Prototype
-> Object.Prototype
实例除了具备Parent.Prototype
提供的方法之外,还具备 React.Component.Prototype
原型上提供的方法:forceUpdate 、isReactComponent 、setState
React类组件
将上文中的无状态函数组件转换成类组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class ClassCom extends React.Component { static propTypes = { sumA : PropTypes .number .isRequired , sumB : PropTypes .number .isRequired } constructor (props ) { super (props); this .state = { sumA : props.sumA , sumB : props.sumB , count : 0 } } UNSAFE_componentWillMount ( ) { console .log ('组件将要挂载' ) } render ( ) { console .log ('渲染' ); let { sumA, sumB,count } = this .state ; return ( <div > <div > <div > sumA: {sumA}</div > <div > sumB: {sumB}</div > <div > count: {count}</div > </div > <div > <button onClick ={() => { this.setState({sumA: sumA+1}); console.log('sumA', sumA) }}>sumA增加</button > <button onClick ={() => { this.setState({sumB: sumB+1}); // 修改状态并触发视图更新 console.log('sumB', sumB) }}>sumB增加</button > <button onClick ={() => { this.state.count = sumA + sumB; this.forceUpdate(); // 强制更新视图 console.log('count', count) }}>count = sumA + sumB</button > </div > </div > ) } componentDidMount ( ) { console .log ('组件挂载完成' ) } shouldComponentUpdate (nextProps, nextState ) { console .log ('nextProps' , nextProps); console .log ('nextState' , nextState); return true ; } UNSAFE_componentWillUpdate ( ) { console .log ('组件将要更新' ) } componentDidUpdate ( ) { console .log ('组件更新完成' ) } } export default ClassCom ;
类组件第一次渲染逻辑
类组件更新状态的渲染逻辑
PS:如果基于this.forceUpdate()方法强制更新视图,会跳过shouldComponentUpdate周期函数的校验,直接从UNSAFE_componentWillUpdate开始进行更新(这通常是不推荐的,因为它可能导致不可预测的渲染行为)
父组件更新导致子组件需要更新的逻辑
React在执行组件树的更新时遵循深度优先遍历
的原则。当React开始渲染或更新过程时,它会从根组件开始,并按照深度优先的顺序逐个处理每个组件。这意味着,在更新父组件之前,React会先调用所有子组件的渲染方法,并在必要时进行更新。
需要注意的是,父组件的componentDidUpdate
是在所有子组件的componentDidUpdate
调用之后才会被调用的。这保证了子组件的props来自于其父组件最新的状态,并反映了React的自顶向下数据流的设计原则。
PureComponent和Component的区别 React.PureComponent
和 React.Component
都是 React 应用中创建类组件的方式,但它们在组件的更新行为上有一些关键的区别。
React.Component React.Component
是创建类组件的基础。如果一个组件继承自 React.Component
,那么它需要手动实现 shouldComponentUpdate
方法来确定组件是否需要更新。默认情况下,当组件的 props 或 state 发生变化时,组件会重新渲染。
React.PureComponent React.PureComponent
与 React.Component
相似,但它通过对 props 和 state 进行浅比较来实现了一个具有浅层比较能力的 shouldComponentUpdate
方法。如果组件的 props 和 state 之前和现在都相等(浅比较),那么组件不会重新渲染。
这意味着使用 PureComponent
可以提高性能,因为它减少了不必要的渲染。但是,使用时需要确保所有的 props 和 state 都是不可变的,因为 PureComponent
的浅比较可能会错过深层数据结构的变化,从而导致组件不更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class MyPureComponent extends React.PureComponent { state = { arr : [10 ,20 ,30 ] } render ( ) { let { arr } = this .state ; return ( <div > { arr.map((item, index) => { return <div key ={index} > {item}</div > }) } <button onClick ={() => { arr.push(40); console.log('===================================='); console.log(arr); console.log('===================================='); this.setState({arr}); }}>add div</button > </div > ) } }
点击按钮后:
可见组件state状态已改变,但页面并未更新,只因PureComponent
在shouldComponentUpdate
中对state进行浅比较未发现变动的地方。
向数组arr中push数据,将arr的值改变了,引用地址未变,浅比较无法比较出区别
1 2 3 4 5 6 7 <button onClick={() => { arr.push (40 ); console .log ('====================================' ); console .log (arr); console .log ('====================================' ); this .setState ({arr :[...arr]}); }}>add div</button>
主要区别
更新机制 :React.Component
默认情况下不实现 shouldComponentUpdate
,而 React.PureComponent
实现了一个具有浅比较逻辑的 shouldComponentUpdate
。
性能优化 :React.PureComponent
通过避免不必要的更新来优化性能,但前提是组件的 props 和 state 都是不可变的。
使用场景 :如果你知道一个组件的 props 和 state 结构简单,不会发生深层数据变化,那么使用 PureComponent
可以很容易地获得性能提升。相反,如果有复杂的数据结构或需要深层比较,可能就需要手动优化 shouldComponentUpdate
,或者使用 React.Component
。
总的来说,PureComponent
可以减少组件的渲染次数从而优化性能,但是在使用时需要小心数据结构的变化。如果组件经常依赖深层数据变动或者数据变动非常复杂,那么可能需要使用 React.Component
并手动实现 shouldComponentUpdate
以获得更精确的控制。
手动实现对象浅比较 在Component
中手动实现shouldComponentUpdate内对象浅比较以达到PureComponent
的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 const isObject = (obj ) => { return obj !== null && /^(object|function)$/ .test (typeof obj); } const shallowEqual = (obj1, obj2 ) => { if (!isObject (obj1) || !isObject (obj2)) return false ; if (obj1 === obj2) return true ; let keys1 = Object .keys (obj1), keys2 = Object .keys (obj2); if (keys1.length !== keys2.length ) return false ; for (let i = 0 ; i< keys1.length ; i++) { let key = keys1[i]; if (!obj2.hasOwnProperty (key) || !Object .is (obj1[key], obj2[key])) return false ; } return true ; } class MyComponent extends React.Component { state = { arr : [10 ,20 ,30 ] } render ( ) { let { arr } = this .state ; return ( <div > { arr.map((item, index) => { return <div key ={index} > {item}</div > }) } <button onClick ={() => { arr.push(40); console.log('===================================='); console.log(arr); console.log('===================================='); this.setState({arr}); }}>add div</button > </div > ) } shouldComponentUpdate (nextProps, nextState ) { let { props, state } = this ; return !shallowEqual (props, nextProps) || !shallowEqual (state, nextState); } }
测试效果与PureComponent
一致。