前言:
看了一晚上react源码,有些感悟,趁着还没忘,赶紧记录下来。
1.react的执行机制带来的两个问题
之前看react文档,说是在不要再以下两种情况下使用hooks:
1.1 只在 React
函数中调用 Hook
;
1.2 不要在循环、条件或嵌套函数中调用 Hook
;
第一条好理解,因为react hooks本来就是给函数组件用的,在普通函数中使用没有意义。
第二条就令人困扰了,为啥?
import React, { useState } from "react"; let isMounted = false; function PersonalInfoComponent() { let name, age, career, setName; console.log("isMounted", isMounted); if (!isMounted) { [name, setName] = useState("接着奏乐接着舞。"); [age] = useState(18); isMounted = true; } [career] = useState("前端开发"); console.log("career", career); return ( <div className="personalInfo"> <p>姓名:{name}</p> <p>年龄:{age}</p> <p>职业:{career}</p> <button onClick={() => { setName("接着奏乐接着舞2。"); }} > 修改姓名 </button> </div> ); } export default PersonalInfoComponent;
1.3 刨析:
通过源码得知:Hooks在首次渲染和更新渲染(除了首次渲染之外的)时分别执行了不同的逻辑,他的执行是一个链式的表(A==》B==》C),即:首次渲染时构建这个链表,其他时候都是根据这个链表依次遍历,是有顺序的,就和数组遍历差不多的道理,那么上面的情况就明朗了,上面用了判断有时候3个hook成立有时候1个hook成立,那就很可能出错。
1.4 小结:为什么不要在循环、条件或嵌套函数中调用 Hook?
答案是:hooks的首次渲染和更新渲染执行的是不同的逻辑,第一次渲染时会构建链表,后续的渲染会根据这个链表依次遍历渲染。
因此在循环(会多会少)、条件(会多会少)、嵌套函数(有时执行有时不执行)中均不可使用(导致不可预测的问题)
1.4 相应的源码:
-----定义useState 开始------------ export function useState(initialState) { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); } -----定义useState 结束------------ 当函数组件进入 render 阶段 的时候, 如果发现组件内存在 Hooks , 那么会调用 renderWithHooks (opens new window)方法, 在这个方法中会根据不同渲染情况对当前的 dispatcher 进行赋值 ReactCurrentDispatcher.current = current === null || current.memoizedState === null ? HooksDispatcherOnMount : HooksDispatcherOnUpdate; --------最终的结果-------- 通过上面的代码可以看到: 第一次渲染执行的是 :HooksDispatcherOnMount函数 后面的渲染执行的是:HooksDispatcherOnUpdate函数
2.setState到底是同步还是异步?
先说结论:时而同步时而异步,结合原理具体说说?
通过研究源码可以得出:setState的执行是批量更新+开关锁
意思就是,react不会你改一次它渲染一次,而是攒着,等你改完了,我只需要修改一次就好了,我写一个伪代码包含开关锁的使用哈:
看我图上的标注后是否恍然大悟,那么上图会打印1 1这个结果也是可以接受了。
这就会使得setState是异步。
2.1 setState小结:
setState 并不是单纯同步或异步的,它的表现会因调用场景的不同而不同:
在 React 钩子函数及合成事件中,它表现为异步;
而在 setTimeout 、 setInterval 等函数中,包括在 DOM 原生事件中,它都表现为同步(原因是,这个锁是同步执行,而异步肯定比同步晚,等执行settimeout里面的时候锁已经开了)
这种差异,本质上是由 React 事务机制和批量更新机制的工作方式来决定的。
3. 逻辑复用:高阶组件(HOC)、render props、hooks
3.1 举例场景:
我要判断用户是否登录来展示不同的内容,且这个功能我要在跟多个地方使用。
3.2 高阶组件的方式:
下面的代码例子就是使用高阶组件封装的,我们只需在要用的时候,传入组件即可
import checkUserAccess from './utils const withCheckAccess = (WrappedComponent) => { //checkUserAccess()方法返回一个布尔值来确定是否登录,是咱们封装的逻辑 const isAccessible = checkUserAccess() // 将 isAccessible(是否登录) 这个信息传递给目标组件 const targetComponent = (props) => ( <div className="wrapper-container"> <WrappedComponent {...props} isAccessible={isAccessible} /> </div> ); return targetComponent; }; ----------使用----------- const EnhancedAComponent = withCheckAccess(Acomponent);
3.3 高阶组件小结:
高阶组件说白了,就是一个函数接收一个组件,经过函数处理返回一个加强过的组件。
3.4 Render props方式:
import checkUserAccess from './utils // 定义 render props 组件 const CheckAccess = (props) => { const isAccessible = checkUserAccess() // 将 isAccessible(是否登录) 这个信息传递给目标组件 return <React.Fragment > { props.children({ ...props, isAccessible }) } < /React.Fragment> }; -------使用如下--------- <CheckAccess> { (props) => { const { isAccessible } = props; return <ChildComponent {...props} isAccessible={isAccessible} /> } } </CheckAccess>
3.5 Render props方式小结:
Render props说白了,就是应该是一个React 组件,并且它的子组件需要以函数形式存在。。