1.React Hooks实现原理
如果让我们来实现一个React Hooks我们如何实现呢?好像毫无头绪,可以先看一个简单的useState:(这部分内容只是帮我们更好的理解Hooks工作原理,想了解Hooks最佳实践可以直接查看React 生产应用)
function App(){
const [count,setCount] = useState(0);
useEffect(()=>{
console.log(`update--${count}`)
},[count])
return(
<div>
<button onClick={()=>setCount(count+1)}>
{`当前点击次数:${count}`}
</button>
</div>
)
}
复制代码
- 实现setState
上面可以看出来当调用useState时;会返回一个变量和一个函数,其参数时返回变量的默认值。我们先构建如下的useState函数:
function useState(initVal) {
let val = initVal;
function setVal(newVal) {
val = newVal;
render(); // 修改val后 重新渲染页面
}
return [val, setVal];
}
复制代码
我们可以在代码中来使用useState--查看demo;
不出意外当我们点击页面上的按钮时候,按钮中数字并不会改变;看控制台中每次点击都会输出0,说明useState是执行了。由于val是在函数内部被声明的,每次useState都会重新声明val从而导致状态无法被保存,因此我们需要将val放到全局作用域声明。
let val; // 放到全局作用域
function useState(initVal) {
val = val|| initVal; // 判断val是否存在 存在就使用
function setVal(newVal) {
val = newVal;
render(); // 修改val后 重新渲染页面
}
return [val, setVal];
}
复制代码
修改useState后,点击按钮时按钮就发生改变了--修改后Demo
- 实现useEffect
useEffect是一个函数,有两个参数一个是函数,一个是可选参数-数组,根据第二个参数中是否有变化来判断是否执行第一个参数的函数:
// 实现第一版 不考虑第二个参数
function useEffect(fn){
fn();
}
复制代码
ok!不考虑第二个参数很简单,其实就是执行下函数--这里查看Demo(控制台中能看到useEffect执行了)。但是我们需要根据第二个参数来判断是否执行,而不是一直执行。所以我们还需要有一个判断逻辑去执行函数。
let watchArr; // 为了记录状态变化 放到全局作用域
function useEffect(fn,watch){
// 判断是否变化
const hasWatchChange = watchArr?
!watch.every((val,i)=>{ val===watchArr[i] }):true;
if( hasWatchChange ){
fn();
watchArr = watch;
}
}
复制代码
完成好useEffect我们在去测试下 --测试demo
打开测试页面我们每次点击按钮,控制台会打印当前更新的count;到目前为止,我们模拟实现了useState和useEffect可以正常工作了。不知道大家是否还记得我们通过全局变量来保证状态的实时更新;如果组件中要多次调用,就会发生变量冲突的问题,因为他们共享一个全局变量。如何解决这个问题呢?
- 解决同时调用多个
useStateuseEffect的问题
// 通过数组维护变量
let memoizedState = [];
let currentCursor = 0;
function useState(initVal) {
memoizedState[currentCursor] = memoizedState[currentCursor] || initVal;
function setVal(newVal) {
memoizedState[currentCursor] = newVal;
render();
}
// 返回state 然后 currentCursor+1
return [memoizedState[currentCursor++], setVal];
}
function useEffect(fn, watch) {
const hasWatchChange = memoizedState[currentCursor]
? !watch.every((val, i) => val === memoizedState[currentCursor][i])
: true;
if (hasWatchChange) {
fn();
memoizedState[currentCursor] = watch;
currentCursor++; // 累加 currentCursor
}
}
复制代码
修改核心是将useState,useEffect按照调用的顺序放入memoizedState中,每次更新时,按照顺序进行取值和判断逻辑--查看Demo
- 图解React Hooks 原理
如上图我们根据调用hooks顺序,将hooks依次存入数组memoizedState中,每次存入时都是将当前的currentcursor作为数组的下标,将其传入的值作为数组的值,然后在累加currentcursor,所以hooks的状态值都被存入数组中memoizedState。
上面状态更新图,我们可以看到执行setCount(count + 1)或setData(data + 2)时,先将旧数组memoizedState中对应的值取出来重新复值,从而生成新数组memoizedState。对于是否执行useEffect通过判断其第二个参数是否发生变化而决定的。
这里我们就知道了为啥官方文档介绍:不要在循环,条件或嵌套函数中调用 Hooks, 确保总是在你的 React 函数的最顶层调用他们。因为我们是根据调用hooks的顺序依次将值存入数组中,如果在判断逻辑循环嵌套中,就有可能导致更新时不能获取到对应的值,从而导致取值混乱。同时useEffect第二个参数是数组,也是因为它就是以数组的形式存入的。
当然,react官方不会像我们这么粗暴的方式去实现的,想了解官方是如何实现可以去这里查看。
2.高阶组件
1.定义:高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
2.hoc:高阶组件是参数为组件,返回值为新组件的函数
3.HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用
- 优点:复用了业务逻辑提高了开发效率,同时还方便后期维护
- 缺点:
- 高阶组件的props都是直接透传下来,无法确实子组件的props的来源。
- 可能会出现props重复导致报错。
- 组件的嵌套层级太深。
- 会导致ref丢失。
3.mixin
React mixin 是通过React.createClass创建组件时使用的,现在主流都是通过ES6方式创建react组件,官方因为mixin不好追踪变化以及影响性能,所以放弃了对其支持,同时也不推荐我们使用。这里就简单介绍下mixin:
mixin的原理其实就是将[mixin]里面的方法合并到组件的prototype上
4.mobox 和 redux 有什么区别?
(1)共同点
- 为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;
- 某一状态只有一个可信数据来源(通常命名为store,指状态容器);
- 操作更新状态方式统一,并且可控(通常以action方式提供更新状态的途径);
- 支持将store与React组件连接,如react-redux,mobx- react;
(2)区别 Redux更多的是遵循Flux模式的一种实现,是一个 JavaScript库,它关注点主要是以下几方面∶
-
Action∶ 一个JavaScript对象,描述动作相关信息,主要包含type属性和payload属性∶
o type∶ action 类型; o payload∶ 负载数据; 复制代码 -
Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;
-
Store∶ 管理action和reducer及其关系的对象,主要提供以下功能∶
o 维护应用状态并支持访问状态(getState()); o 支持监听action的分发,更新状态(dispatch(action)); o 支持订阅store的变更(subscribe(listener)); 复制代码 -
异步流∶ 由于Redux所有对store状态的变更,都应该通过action触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入React组件中,就需要使用其他框架配合管理异步任务流程,如redux-thunk,redux-saga等;
Mobx是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶
- Action∶定义改变状态的动作函数,包括如何变更状态;
- Store∶ 集中管理模块状态(State)和动作(action)
- Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据
对比总结:
- redux将数据保存在单一的store中,mobx将数据保存在分散的多个store中
- redux使用plain object保存数据,需要手动处理变化后的操作;mobx适用observable保存数据,数据变化后自动处理响应的操作
- redux使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx中的状态是可变的,可以直接对其进行修改
- mobx相对来说比较简单,在其中有很多的抽象,mobx更多的使用面向对象的编程思维;redux会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
- mobx中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而redux提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
5. hooks的一个更新流程?
调 用 useState 返 回的第二个参数内部调用的就是 dispatchAction。dispatchAction内部会创建一个update对象,这个对象会赋值 给fiber节点(当前触发更新的)。fiber对应是app这个函数组件。fiber会传给scheduleUpdateOnFilber函数,调度这次更新,然后这个fiber就会跟其他的fiber抢优先级,谁优先级高,就会先进去render阶段,diff算法,看哪些dom要变化。然后就会进入commitRoot,commitRoot传的参数是root,root是fiberRootNode,也是整个应用的根节点,root.current是当前应用的根节点,也就是ReactDom.render(,rootEl)对应的这个应用的跟节点,root.current.firstEffect是本次要执行更新的节点有哪些,它是一个链表,它上边有一个nextEffect字段,指下一个要更新的节点,要是还是下下个,就下一个对象的nextEffect就指向下下个。从而形成一个链表。遍历这个链表,就会知道有哪些dom要执行更新操作。