React 16.7-alpha中新增了新功能:Hooks
。总结他的功能就是:让FunctionalComponent
具备ClassComponent
的功能。前端
import React, { useState, useEffect } from 'react'
function FunComp(props) {
const [data, setData] = useState('initialState')
function handleChange(e) {
setData(e.target.value)
}
useEffect(() => {
subscribeToSomething()
return () => {
unSubscribeToSomething()
}
})
return (
<input value={data} onChange={handleChange} /> ) } 复制代码
按照Dan的说法,设计Hooks
主要是解决ClassComponent
的几个问题:react
bind
,this
指向不明确这些确实是存在的问题,好比咱们若是用了react-router
+redux
+material-ui
,极可能随便一个组件最后export
出去的代码是酱紫的:git
export default withStyle(style)(connect(/*something*/)(withRouter(MyComponent)))
复制代码
这就是一个4层嵌套的HOC
组件github
同时,若是你的组件内事件多,那么你的constructor
里面可能会酱紫:编程
class MyComponent extends React.Component {
constructor() {
// initiallize
this.handler1 = this.handler1.bind(this)
this.handler2 = this.handler2.bind(this)
this.handler3 = this.handler3.bind(this)
this.handler4 = this.handler4.bind(this)
this.handler5 = this.handler5.bind(this)
// ...more
}
}
复制代码
虽然最新的class
语法能够用handler = () => {}
来快捷绑定,但也就解决了一个声明的问题,总体的复杂度仍是在的。redux
而后还有在componentDidMount
和componentDidUpdate
中订阅内容,还须要在componentWillUnmount
中取消订阅的代码,里面会存在不少重复性工做。最重要的是,在一个ClassComponent
中的生命周期方法中的代码,是很难在其余组件中复用的,这就致使了了代码复用率低的问题。数组
还有就是class
代码对于打包工具来讲,很难被压缩,好比方法名称。前端工程师
更多详细的你们能够去看ReactConf
的视频,我这里就很少讲了,这篇文章的主题是从源码的角度讲讲Hooks
是如何实现的react-router
首先useState
是一个方法,它自己是没法存储状态的框架
其次,他运行在FunctionalComponent
里面,自己也是没法保存状态的
useState
只接收一个参数initial value
,并看不出有什么特殊的地方。因此React在一次从新渲染的时候如何获取以前更新过的state
呢?
在开始讲解源码以前,你们先要创建一些概念:
JSX
翻译过来以后是React.createElement
,他最终返回的是一个ReactElement
对象,他的数据解构以下:
const element = {
$$typeof: REACT_ELEMENT_TYPE, // 是不是普通Element_Type
// Built-in properties that belong on the element
type: type, // 咱们的组件,好比`class MyComponent`
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
复制代码
这其中须要注意的是type
,在咱们写<MyClassComponent {...props} />
的时候,他的值就是MyClassComponent
这个class
,而不是他的实例,实例是在后续渲染的过程当中建立的。
每一个节点都会有一个对应的Fiber
对象,他的数据解构以下:
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null; // 就是ReactElement的`$$typeof`
this.type = null; // 就是ReactElement的type
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.firstContextDependency = null;
// ...others
}
复制代码
在这里咱们须要注意的是this.memoizedState
,这个key
就是用来存储在上次渲染过程当中最终得到的节点的state
的,每次执行render
方法以前,React会计算出当前组件最新的state
而后赋值给class
的实例,再调用render
。
因此不少不是很清楚React原理的同窗会对React的ClassComponent
有误解,认为state
和lifeCycle
都是本身主动调用的,由于咱们继承了React.Component
,它里面确定有不少相关逻辑。事实上若是有兴趣能够去看一下Component
的源码,大概也就是100多行,很是简单。因此在React中,class
仅仅是一个载体,让咱们写组件的时候更容易理解一点,毕竟组件和class
都是封闭性较强的
在知道上面的基础以后,对于Hooks
为何可以保存无状态组件的原理就比较好理解了。
咱们假设有这么一段代码:
function FunctionalComponent () {
const [state1, setState1] = useState(1)
const [state2, setState2] = useState(2)
const [state3, setState3] = useState(3)
}
复制代码
先来看一张图
在咱们执行functionalComponent
的时候,在第一次执行到useState
的时候,他会对应Fiber
对象上的memoizedState
,这个属性原来设计来是用来存储ClassComponent
的state
的,由于在ClassComponent
中state
是一整个对象,因此能够和memoizedState
一一对应。
可是在Hooks
中,React并不知道咱们调用了几回useState
,因此在保存state
这件事情上,React想出了一个比较有意思的方案,那就是调用useState
后设置在memoizedState
上的对象长这样:
{
baseState,
next,
baseUpdate,
queue,
memoizedState
}
复制代码
咱们叫他Hook对象。这里面咱们最须要关心的是memoizedState
和next
,memoizedState
是用来记录这个useState
应该返回的结果的,而next
指向的是下一次useState
对应的`Hook对象。
也就是说:
hook1 => Fiber.memoizedState
state1 === hook1.memoizedState
hook1.next => hook2
state2 === hook2.memoizedState
hook2.next => hook3
state3 === hook2.memoizedState
复制代码
每一个在FunctionalComponent
中调用的useState
都会有一个对应的Hook
对象,他们按照执行的顺序以相似链表的数据格式存放在Fiber.memoizedState
上
重点来了:就是由于是以这种方式进行state
的存储,因此useState
(包括其余的Hooks)都必须在FunctionalComponent
的根做用域中声明,也就是不能在if
或者循环中声明,好比
if (something) {
const [state1] = useState(1)
}
// or
for (something) {
const [state2] = useState(2)
}
复制代码
最主要的缘由就是你不能确保这些条件语句每次执行的次数是同样的,也就是说若是第一次咱们建立了state1 => hook1, state2 => hook2, state3 => hook3
这样的对应关系以后,下一次执行由于something
条件没达成,致使useState(1)
没有执行,那么运行useState(2)
的时候,拿到的hook
对象是state1
的,那么整个逻辑就乱套了,因此这个条件是必需要遵照的!
上面讲了Hooks
中state
是如何保存的,那么接下去来说讲如何更新state
咱们调用的调用useState
返回的方法是酱紫的:
var dispatch = queue.dispatch = dispatchAction.bind(null, currentlyRenderingFiber$1, queue);
return [workInProgressHook.memoizedState, dispatch];
复制代码
调用这个方法会建立一个update
var update = {
expirationTime: _expirationTime,
action: action,
callback: callback !== undefined ? callback : null,
next: null
}
复制代码
这里的action
是咱们调用setState1
传入的值,而这个update
会被加入到queue
上,由于可能存在一次性调用屡次setState1
的清空(跟React的batchUpdate有关,之后有机会讲。)
在收集完这全部update
以后,会调度一次React
的更新,在更新的过程当中,确定会执行到咱们的FunctionalComponent
,那么就会执行到对应的useState
,而后咱们就拿到了Hook
对象,他保存了queue
对象表示有哪些更新存在,而后依次进行更新,拿到最新的state
保存在memoizedState
上,而且返回,最终达到了setState
的效果。
其实本质上跟ClassComponent
是差很少的,只不过由于useState
拆分了单一对象state
,因此要用一个相对独特的方式进行数据保存,并且会存在必定的规则限制。
可是这些条件彻底不能掩盖Hooks
的光芒,他的意义是在是太大了,让React
这个 函数式编程范式的框架终于摆脱了要用类来建立组件的尴尬场面。事实上类的存在乎义确实不大,好比PuerComponent
如今也有对应的React.memo
来让函数组件也能达到相同的效果。
最后,由于真的要把源码摊开来说,就会涉及到一些其余的源码内容,好比workInProgress => current
的转换,expirationTime
涉及的调度等,反而会致使你们没法理解本篇文章的主体Hooks
,因此我在写完完整源码解析后又总结概括了这篇文章来单独发布。但愿能帮助各位童鞋更好得理解Hooks
,并能大胆用到实际开发中去。
由于:真的很好用啊!!!
目前react-hot-loader
不能和hooks
一块儿使用,详情,因此你能够考虑等到正式版再用。
我是Jocky,一个专一于React技巧和深度分析的前端工程师,React绝对是一个越深刻学习,越能让你以为他的设计精巧,思想超前的框架。关注我获取最新的React动态,以及最深度的React学习。更多的文章看这里