内容简介:最近终于是下定决心将我负责的一个公司内部系统由react v15.4.1升级到v16.8.6。v16.8.6中最被推崇的一个特性应该就是react hook了,实际上手后为能更顺手的使用也是花了2个多小时的时间把react hook的实现源码看了一遍。一个函数式组件要在这个组件中触发更新要怎么做,下面我们就做点努力,改造一下。
最近终于是下定决心将我负责的一个公司内部系统由react v15.4.1升级到v16.8.6。v16.8.6中最被推崇的一个特性应该就是react hook了,实际上手后为能更顺手的使用也是花了2个多小时的时间把react hook的实现源码看了一遍。
useState
一,探索版本
一个函数式组件
const FunctionComponent = () => {
return (
<div>hello functional component</div>
)
}
复制代码
要在这个组件中触发更新要怎么做,下面我们就做点努力,改造一下。
改造1
// 下面的实现中click事件被触发然后执行sayHello对say赋值hello,
// 这个过程并没有触发react的更新机制,所以页面不会显示hello
const FunctionComponent = () => {
let say = 'hello functional component'
const sayHello = () => {
say = 'hello'
}
return (
<>
<div>{say}</div>
<button onClick={sayHello}>say hello</button>
</>
)
}
复制代码
改造2
在1中我们已经改变了say的值,现在我只需想办法触发react的更新就可以了。如何手动触发react的更新,我想到了 forceUpdate
// 需要将say作为一个FunctionComponent的外部变量
// 避免在更新触发后函数式组件执行对say重新赋值
let say = 'hello functional component'
const FunctionComponent = props => {
const sayHello = () => {
// 改变状态
say = 'hello'
// 手动触发父组件的更新从而更新自己
props.update()
}
return (
<>
<div>{say}</div>
<button onClick={sayHello}>say hello</button>
</>
)
}
class Stage extends React.Component {
// 通过触发父组件的更新来更新函数式组件
// update属性用于触发更新函数式组件
handleUpdate = () => {
this.forceUpdate()
}
render() {
return (
<div className="App">
<FunctionComponent update={this.handleUpdate} />
</div>
);
}
}
复制代码
到此我们已经达到了使用functional组件实现类似class组件更新机制的目的。
通过上面的探索在不借助useState的情况我们也能实现functional组件的状态更新。
问题是我们每次都得手动调用forceUpdate(),不够友好。而且很容易相信这个方式性能极低。
二,useState版本:
在整个hook的使用中有两个对象你不能忽视(具体区别请查看源码):
- HooksDispatcherOnMountInDEV —— functional组件在首次调用是使用
- HooksDispatcherOnUpdateInDEV —— functional组件在更新时调用
使用示例
const [count, setCount] = useState(0) 复制代码
1,useState(0)发生了什么
每次执行useState都会生成一个hook对象
var hook = {
memoizedState: 传入的初时值,如果useState接收的是函数则是函数执行的结果,
baseState: null,
queue: 保存对state的更新队列,
baseUpdate: null,
next: null
};
复制代码
在一个functional组件中调用多少次 useState()
则会新建多少个hook对象,为了维护这些hook对象react使用了一个WorkInProgressHook的链表保存——这一步处理你可以理解成我们在探索版本中将functional组件的状态变量say做全局处理相同的目的
2,返回了什么count = ?、setCount = ?
var queue = hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState
};
var dispatch = queue.dispatch = dispatchAction.bind(null,
currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
复制代码
-
hook.memoizedState —— 其实就是我们探索版本中的say只不过我们是通过
window.say保存,而它通过之前创建的WorkInProgressHook.hook保存 -
dispatch —— 其实也就是我们探索版本中的
props.update,探索版本中我们使用forceUpdate更新父组件的方式来触发更新,而它采用了一个更底层的方法scheduleWork。不知道scheduleWork?请参考我之前的 react fiber简单了解下
总结:
functional组件有两个特性注定他不能拥有状态:
1,一个纯函数每次调用都是全新的东西。要破坏这种特性就需要让这个函数产生副总用——依赖全局变量(window.say、WorkInProgressHook.hook)。
2,之前版本中react官方提供 setState
用于改变状态触发更新,但这个只存在与class组件,新版本的 dispatch
给了我们多一个选择
小知识点:functional组件执行过程中生成的 WorkInProgressHook
最终会保存到当前组件对应fiber对象上
// WorkInProgressHook是一个链表结构 // firstWorkInProgressHook表示这个链表头部指针 var renderedWork = currentlyRenderingFiber$1; renderedWork.memoizedState = firstWorkInProgressHook; 复制代码
useEffect
当你认真看完前面 useState
的实现过程,你会发现这样一个过程:
- 生成hook
- 用WorkInProgressHook链表保存
- 最终将WorkInProgressHook保存为当前fiber的memoizedState
如果你对react的fiber有一定了解的话对memoizedState一定不会陌生,我们在class组件中的state最终也是保存到fiber的memoizedState
手动分割线-----------------------------------------
useEffect发生了什么
- 生成effect
var effect = {
tag: tag,
create: useEffect的第一个参数传入的函数,
destroy: destroy,
deps: useEffect的第二个参数更新依赖,
// Circular
next: null
};
复制代码
- 用componentUpdateQueue链表保存
- 最终将componentUpdateQueue保存为当前fiber的updateQueue,
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher; renderedWork.updateQueue = componentUpdateQueue; 复制代码
如果你对fiber的调用过程一定了解的话updateQueue你也不会陌生,这个地方存有react diff出来的副作用,用于在commit阶段执行。
总结:
和useState有相同的实现过程。
不同点:
- useState需要处理影响页面更新的数据,而在react fiber中将此类数据全部保存到memoizedState,对memoizedState的使用发送在react fiber的reconciler阶段
- useEffect的目的是在组件mounted后进行作用,fiber中将此类操作作为effect维护这一个链表,effect的触发过程发生在react fiber的commit阶段
useCallback
和useState的生成过程基本类似,不过生成的hook对象有点区别
var hook = {
memoizedState: [传入的callback,传人的更新依赖],
...
};
复制代码
useCallback算是我个人比较喜欢的一个功能
class Stage extends React.Component{
handleClick = () => {
console.log('what are you 弄啥呢?')
}
render () {
return (
<>
<!-- 大多数情况下都会建议这个写法 -->
<div onClick={this.handleClick}>弄你</div>
<!-- 一般会吐槽这个写法 -->
<div onClick={() => this.handleClick()}>弄你</div>
</>
)
}
}
复制代码
第一种写法保证执行render时属性onClick的值都相同;而第二种写法每次执行render都会生成一个新的函数,所以在diff时onClick每次都不同。
useCallback就用来帮助我们在functional组件中实现了第一种性能更优的方式
useMemo
和useState的生成过程基本类似,不过生成的hook对象有点区别
var hook = {
memoizedState: [计算过程的结果,传人的更新依赖],
...
复制代码
useMemo同样是我个人比较喜欢的一个功能,在这之前一直希望有官方的支持,简单说就是对一个计算过程的结果进行缓存。使用过vue的同学可以把它想象成vue的computed属性( vue computed属性的数据响应和依赖缓存实现过程 )。从这一点上看vue领先react好几年~~~~哈哈哈哈哈哈~~~~
hook的第二个参数
官方提供的hook api很多,其中有几个可以接受第二个参数(不传,或者是一个数组)。
作用:在某个依赖项改变时重新操作第一个参数
如果你使用过React.PureComponent就会知道,他通过对新旧props做一个浅比较来判断是否需要更新组件。这第二个参数的原理同样如此。
// 首先对deps做是否为空的判断
if (nextDeps !== null) {
var prevDeps = prevState[1];
// 然后比较新旧deps
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
// 比较新旧deps
function areHookInputsEqual(nextDeps, prevDeps) {
if (prevDeps === null) {
return false;
}
// 对传入的数组成员做浅比较
for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
复制代码
其它hook
剩下的几个hook API大家有兴趣可以自行研读源码,实现过程基本和上面几个相同的思路
总结
最后想说的是没有黑魔法,如果你react的更新机制、fiber的过程、以及函数的特性有清晰的认识,理解hook也是很容易的。
这篇文章本质上也是一片源码解读类型的,但是很少涉及到一些具体实现。通过开篇的一个探索版本模糊认识hook的设计思路。所有这一切都是建立在reactv16版本fiber的优秀设计上。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据结构、算法与应用
(美)Sartaj Sahni / 汪诗林、孙晓东、等 / 机械工业出版社 / 2000-01 / 49.00
本书是关于计算机科学与工程领域的基础性研究科目之一――数据结构与算法的专著。 本书在简要回顾了基本的C++ 程序设计概念的基础上,全面系统地介绍了队列、堆栈、树、图等基本数据结构,以及贪婪算法、分而治之算法、分枝定界算法等多种算法设计方法,为数据结构与算法的继续学习和研究奠定了一个坚实的基础。更为可贵的是,本书不仅仅介绍了理论知识,还提供了50多个应用实例及600多道练习题。 本书......一起来看看 《数据结构、算法与应用》 这本书的介绍吧!