一些react
一些react知识点
JSX
JSX的本质是什么,它和JS之间到底是什么关系?
JSX是JavaScript的一种语法扩展,它和模板语言很接近,但是它充分具备JavaScript的能力。
为什么要用JSX?不用会有什么后果?
1.提升开发效率;
2.组件化开发;
3.更好的抽象层次;JSX背后的功能模块是什么,这个功能模块都做了哪些事情?
JSX会被编译为
React.createElement()
,React.createElement()
将返回一个叫作“React Element”的JS对象【虚拟DOM】,由babel来完成。组件初始化->render方法->生成虚拟DOM->ReactDOM.render方法->真实DOM。
组件更新->render方法->生成新的虚拟DOM->diff->定位出两次虚拟DOM的差异。
React生命周期
React 15的声明周期:
初始化渲染
constructor()
componentWillMount()
render()
componentDidMount() 渲染结束后被触发,DOM已经挂载,可以处理DOM操作
componentWillUnmount() 组件卸载组件更新
componentWillReceiveProps() [父组件触发] 由父组件的更新触发
如果父组件导致组件重新渲染,即使props没有更改也会调用此方法(componentReceiveProps)如果只想处理更改,请确保进行当前值与变更值的比较。
shouldComponentUpdate() [子组件触发] 根据返回值决定是否要进行该方法后面的生命周期
componentWillMount()
componentWillUpdate()
render()
componentDidUpdate()
可以处理DOM操作
componentWillUnmount() 组件卸载
React 16的生命周期:
废弃了componentWillMount()
,新增static getDerivedStateFromProps()
在初始挂载及后续更新时都会被调用。
static getDerivedStateFromProps()
:使用props
来派生/更新state。getDerivedStateFromProps 的存在只有一个目的:让组件在props变化时更新state。
该方法返回一个对象用于更新 state,如果返回 null 则不更新任何内容。
废弃了componentWillUpdate()
,新增getSnapshotBeforeUpdate()
在render之后执行。
废弃了componentWillMount()
。
在fiber架构下,componentWillXXX()
可能会多次执行。
Hook的设计
为什么需要HOOK?
告别难以理解的class;
- this指向问题。
- 生命周期复杂,逻辑杂糅。
解决业务逻辑难以拆分的问题;
- 相同的逻辑分散在不同的生命周期中。
使得状态逻辑复用变得简单可行;
- 自定义Hook
函数组件更加符和React框架的设计理念。ui = f(data);
工作机制
- 只在 React 函数中调用 Hook
- 不要在循环、条件或嵌套函数中调用 Hook
- 要确保 Hooks 在每次渲染时都保持同样的执行顺序
Hooks的正常运作,在底层依赖于顺序链表。
虚拟DOM
虚拟DOM的价值不在性能。
- 研发体验/研发效率的问题,虚拟DOM是数据驱动视图的载体。
- 跨平台的问题,虚拟DOM是DOM结构的抽象,用以表述真实DOM。
- 批量更新,实现集中化的DOM批量更新。
setState到底是同步的还是异步的?
这个问题的讨论要结合具体的版本:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/17。
下面的内容应该在react18之前。
结论:只要是在React管控下的setState,一定是异步的。
在React钩子函数及合成事件中,它表现为异步。
setState异步的动机:避免频繁的re-render。每来一个setState,就将其放进队列里面,时间成熟后对state结果做合并,走一次更新流程。
在setTimeout、setlnterval等函数中包括在 DOM 原生事件中,它都表现为同步。
setState工作流:
1 | isBatchingUpdates = true |
react 会在执行前加一个“锁”来标记是否需要批处理, 如上,react加了锁之后立刻就释放了,然后才会执行setTimeout里的setState, 也就是说setTimeout和原生事件会脱离react的控制。 只有在react控制下才会存在批处理,setState才会有“异步”效果。
https://github.com/reactwg/react-18/discussions/21
为什么需要fiber架构?
在Stack Reconciler的架构下,组件通过树的结构进行维护,当某个组件的状态发生变化的时候,React会递归其所有的子组件,然后寻找该变化产生的影响。递归遍历一旦启动就不能停止,当组件的结构过于复杂的时候,递归的时间会很长,进而导致主线程的阻塞。现代浏览器的刷新率一般为60HZ,这意味着,长时间的递归将导致掉帧的发生,影响体验。
为什么没有Vue fiber?
Vue没有fiber的原因在于,在Vue中数据变化会被拦截到,然后通知相应的watcher进行对应视图的重新渲染。数据发生变化后,Vue能够快速的捕获这种变化,并通知对应的视图进行响应,而在React中,组件的数据发生变化后,React并不能确定这种变化将会对哪些子组件产生影响,因此只能通过深度遍历进行比对来确定。
事件系统
React的事件体系大都通过事件委托来实现,少部分直接绑定在对应的DOM元素上。
原生事件中,一个事件的传播过程要经过2个阶段:
- 事件捕获阶段
- 目标阶段
- 事件冒泡阶段
事件委托:假如<ul>
标签下有若干个<li>
标签,要为每个<li>
标签都添加一个点击事件,点击事件最终都会冒泡到他们的共同的父节点<ul>
上面,因此,可以将要绑定的事件直接绑定在<ul>
标签上。
React合成事件:
- 在底层抹平了不同浏览器的差异
- 在上层面向开发者暴露统一的、稳定的、与 DOM 原生事件相同的事件接口
- 帮助React实现了对所有事件的中心化管控
运行机制
- render阶段:
递归的创建fiber树 - commit阶段:
- before mutation阶段
- 处理DOM节点渲染/删除后的autoFocus、blur逻辑
- 调用
getSnapshotBeforeUpdate
生命周期钩子 - 调度
useEffect
,不是调用。异步执行的原因是防止同步执行的时候阻塞浏览器渲染
- mutation阶段
- 执行DOM操作
- 执行
useLayoutEffect
的销毁函数
- layout阶段
- 类组件:
componentDidMount/componentDidUpdate
生命周期 - 类组件:调用
this.setState
的回调 - 函数组件:调用
useLayoutEffect
的回调 - 函数组件:调度
useEffect
的销毁和回调函数,layout阶段完成后再执行
- 类组件:
- before mutation阶段
fiber架构
- Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
- Reconciler(协调器)—— 负责找出变化的组件
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上
Reconciler:
1 | function workLoopConcurrent() { |
1 | function shouldYieldToHost() { |
fiber节点之间的关联
1 | // 指向父级Fiber节点 |
fiber架构的工作原理
React双缓存fiber树:current Fiber树、workInProgress Fiber树。
- fiberRootNode:应用的根节点。
- rootfiber:调用
ReactDOM.redner
渲染的组件树的根节点。