import React from 'react'
import ReactDOM from 'react-dom'
// React.createElement(
// type,
// [props],
// [...children]
// )
fiber对应关系
child、return、sibling
React.Children.toArray(children) 扁平化规范化react数组
const flatChildren = React.Children.toArray(children)
const newChildren = []
React.Children.forEach(flatChildren, (item) => {
if (React.isValidElement(item)) newChildren.push(item)
})
const lastChildren = React.createElement(`div`, { className: 'last'}, `say goodbye`)
newChildren.push(lastChildren)
const newReactElement = React.cloneElement(reactElement, {}, ...newChildren)
-
@babel/plugin-syntax-jsx 和 @babel/plugin-transform-react-jsx
-
jsx语法实现来源于这两个babel插件:
-
@bable/plugin-syntax-jsx:能够能babel有效的解析jsx语法
-
@bable/plugin-transform-react-jsx:内部调用了@babel/plugin-syntax-jsx,可以把React JSX转化成JS能够识别的createElement格式
-
我们写的jsx会先转换成React.createElement,然后再转化成React.fiber的过程。
-
在React调和渲染fiber节点的时候,如果发现fiber tag是ClassComponent = 1,则按照类组件逻辑处理,如果是FunctionComponent = 0, 则按照函数组件逻辑处理
class Index extends React.Component {
constructor (...arg) {
super(...arg)
}
state = { a: 1 }
static number = 1
handleClick = () => {
console.log(111)
console.log(this)
}
componentDidMount () {
console.log(Index.number, Index.number1)
}
render () {
return <div onClick={this.handleClick}>hello,world</div>
}
}
Index.number1 = 2
Index.prototype.handleClick = () => console.log(222)
console.dir(Index)
function Index () {
console.log(Index.number)
const [message, setMessage] = React.useState('hello, world')
return <div onClick={() => setMessage('let us learn React')}>{message}</div>
}
Index.number = 1
对于类组件来说,底层只需要实例化一次,实例中保存了组件的state等状态。对于每一次更新只需要调用render方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。
一次事件中触发一次setState,在React底层主要做了哪些事?
- 首先,setState会产生当前更新的优先级
- 接下来React会从fiber Root根部fiber向下调和子节点,调和阶段将对比发生更新的地方,找到发生更新的组件,合并state,然后触发render函数,得到最新的UI,完成render阶段
- 接下来到commit阶段,commit阶段,替换真是dom,完成此次更新流程
- 此时仍然在commit阶段,会执行setState中的callback函数,到此为止完成了一次setState全过程
类组件初始化过程中绑定了负责更新的updater对象,对于如果调用setState方法,实际上是React底层调用Updater对象上的enqueueSetState方法
enqueueSetState的作用,就是创建一个update,然后放入当前fiber对象的待更新队列中,最后开启调度更新。
React同一级别更新优先级关系是:
- flushSync中的setState > 正常执行上下文中setState > setTimeout,Promise中的setState.
function Index2 (props) {
const [number, setNumber] = React.useState(0)
React.useEffect(() => {
console.log('监听number变化,此时的number是:'+ number)
}, [number])
const handleClick = () => {
setTimeout(() => {
setNumber(3)
console.log('number3', number)
})
setNumber(1)
console.log('number1', number)
ReactDOM.flushSync(() => {
setNumber(2)
console.log('number2', number)
})
}
console.log(number)
return <div>
<span>{number}</span>
<button onClick={handleClick}>number++</button>
</div>
}
类组件的setState和函数组件的useState有什么异同?
- 首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。
不同点
- 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
- setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
- setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。
schedulUpdateOnFiber
schedulUpdateOnFiber
pureComponent
shouldComponentUpdate
class组件生命周期
在mount阶段,首先执行的constructorClassInstance函数,用来实例化React组件,在实例化组件之后,会调用mountClassInstance组件初始化。
-
组件初始化阶段
constructor getDerivedStateFromProps:是从类上直接绑定的静态方法,传入props、state,返回值将和之前的state合并,作为新的state,传递给组件实例调用。 render componentDidMount
-
更新阶段
getDerivedStateFromProps(nextProps, prevState):用于在初始化和更新阶段,接受父组件的props数据,可以对props进行格式化,过滤等操作,返回值将作为新的state合并到state中,供给视图渲染层消费。 shouldComponentUpdate(newProps, newState, nextContext) shouldComponentUpdate(newProps, newState, nextContext) render getSnapshotBeforeUpdate(prevProps, prevState) componentDidUpdate(prevProps, prevState, snapshot)
-
销毁阶段
componentWillUnmount