函数式编程看React Hooks(一)简单React Hooks实现javascript
函数式编程看React Hooks(二)事件绑定反作用深度剖析html
函数式编程介绍(摘自基维百科)java
函数式编程(英语:functional programming)或称函数程序设计、泛函编程,是一种编程范式,它将计算机运算视为函数运算,而且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础。并且,λ演算的函数能够接受函数看成输入(引数)和输出(传出值)。react
面向对象编程介绍(摘自基维百科)git
面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具备对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象做为程序的基本单元,将程序和数据封装其中,以提升软件的重用性、灵活性和扩展性,对象里的程序能够访问及常常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象github
函数式强调在逻辑处理中不变性。面向对象经过消息传递改变每一个Object的内部状态。二者是大相径庭的编程思想,都具备本身的优点,也由于如此,才使得咱们从 class 组件
转化到 函数组件式
,有一些费解。编程
从 react 的变化能够看出,react 走的道路愈来愈接近于函数式编程,输入输出一致性。固然也不是凭空地去往这个方面,而是为了可以解决更多的问题。如下 三点是 react 官网所提到的 hooks 的动机 zh-hans.reactjs.org/docs/hooks-…segmentfault
本文是为了给后面一篇文章做为铺垫,由于在以后文章的讲解过程当中,你若是了理解了 React Hooks 的原理,再加上一步一步地讲解,你可能会对 React Hooks 中各类状况会恍然大悟。数组
一开始的时候以为 hooks 很是地神秘,写惯了 class 式的组件后,咱们的思惟就会定格在那里,生命周期,state,this等的使用。 所以会以 class 编写的模式去写函数式组件,致使咱们一次又一次地爬坑,接下来咱们就开始咱们的实现方式讲解。(提示:如下是都只是一种简单的模拟方法,与实际有一些差异,可是核心思想是一致的)缓存
咱们先写一个简单的 react 函数式组件。
function Counter(count) {
return (
<div> <div>{count}</div> <button> 点击 </button> </div>
);
}
复制代码
在 React Hooks 还未出现的时候,咱们的组件大多用来直接渲染,不含有状态存储,Function组件没有state,因此也叫SFC(stateless functional component),如今更新叫作FC(functional component)。
为了使得一个函数内有状态,react 使用了一个特别的方法就是 hooks, 其实这是利用闭包实现的一个相似做用域的东西去存储状态,我第一想到的就是利用对象引用存储数据,就像是面向对象同样的方式,存在一个对象中中,经过引用的方式来进行获取。可是 react 为了可以尽量地分离状态,精妙地采用了闭包。
让咱们看看他是如何实现的。(为了尽量简化,我进行了改编)
let _state;
function useState(initialState) {
_state = _state || initialState; // 若是存在旧值则返回, 使得屡次渲染后的依然能保持状态。
function setState(newState) {
_state = newState;
render(); // 从新渲染,将会从新执行 Counter
}
return [_state, setState];
}
复制代码
function Counter() {
const [count, setCount] = useState(0);
return (
<div> <div>{count}</div> <button onClick={() => setCount(count + 1)}> 点击 </button> </div>
);
}
复制代码
演示地址: codesandbox.io/s/dawn-bash…
以上,无论 Counter 从新渲染多少次,经过闭包,依然可以访问到最新的 state,从而达到了存储状态的效果。
再看看 useEffect, 先来看看使用方法。 useEffect(callback, dep?)
, 如下是一个很是简单的使用例子。
useEffect(() => {
console.log(count);
}, [count]);
复制代码
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
}, [count]);
return (
<div> <div>{count}</div> <button onClick={() => setCount(count + 1)}> 点击 </button> </div>
);
}
复制代码
由于函数式不像 class 那样有复杂的生命周期,已经对 hooks 已经熟悉使用的你,可能会知道 useEffect
能够当作,componentdidmount
来使用。可是在这里你直接将他按照顺序执行。在 return
前他会执行。
let _deps = {
args: []
}; // _deps 记录 useEffect 上一次的 依赖
function useEffect(callback, args) {
const hasChangedDeps = args.some((arg, index) => arg !== _deps.args[index]); // 两次的 dependencies 是否彻底相等
// 若是 dependencies 不存在,或者 dependencies 有变化
if (!_deps.args || hasChangedDeps) {
callback();
_deps.args = args;
}
}
复制代码
演示地址: codesandbox.io/s/ecstatic-…
至此,咱们也实现了单个 useEffect。
咱们再来看看, useMemo,其实他也以上实现的方式同样,也是经过闭包来进行存储数据, 从而达到缓存提升性能的做用。
function Counter() {
const [count, setCount] = useState(0);
const computed = () => {
console.log('我执行了');
return count * 10 - 2;
}
const sum = useMemo(computed, [count]);
return (
<div> <div>{count} * 10 - 2 = {sum}</div> <button onClick={() => setCount(count + 1)}> 点击 </button> </div>
);
}
复制代码
接下来咱们来进行实现
let _deps = {
args: []
}; // _deps 记录 useMemo 上一次的 依赖
function useMemo(callback, args) {
const hasChangedDeps = args.some((arg, index) => arg !== _deps.args[index]); // 两次的 dependencies 是否彻底相等
// 若是 dependencies 不存在,或者 dependencies 有变化
if (!_deps.args || hasChangedDeps) {
_deps.args = args;
_deps._callback = callback;
_deps.value = callback();
return _deps.value;
}
return _deps.value;
}
复制代码
演示地址: codesandbox.io/s/festive-p…
那么 useCallback
呢? 其实就是 useMemo
的一个包装,毕竟你缓存函数的返回值,那么我我让返回值为一个函数不就好了?
function useCallback(callback, args) {
return useMemo(() => callback, args);
}
复制代码
能够看到,以上咱们也轻松地实现了 useMemo 。可是有一个问题,以上只是单个函数使用方式,因此接下来咱们还须要处理一下多个函数的状况。
咱们能够按照 preact 的方法来实现。即用数组来实现多个函数的处理逻辑。
核心逻辑就是
第一次声明的时候将 useState, useEffect, useMemo, useCallback 等钩子函数的状态依次存入数组。
更新的时候,将前一次的函数状态值依次取出。
也能够经过如下图来理解
第一次渲染,将每一个状态都缓存到数组中。
每次从新渲染,获取数组中每一个的缓存状态。
如下为了可以清晰地让你们明白原理,进行了一些删减。可是核心逻辑不变。
let currentIndex = 0;
let currentComponent = {
__hooks: []
};
function getHookState(index) {
const hooks = currentComponent.__hooks;
if (index >= hooks.length) {
hooks.push({});
}
return hooks[index];
}
function argsChanged(oldArgs, newArgs) {
return !oldArgs || newArgs.some((arg, index) => arg !== oldArgs[index]);
}
function useState(initialState) {
const hookState = getHookState(currentIndex++);
hookState._value = [
hookState._value ? hookState._value[0] : initialState,
function setState(newState) {
hookState._value[0] = newState;
render(); // 从新渲染,将会从新执行 Counter
}
];
return hookState._value;
}
function useEffect(callback, args) {
const state = getHookState(currentIndex++);
if (argsChanged(state._args, args)) {
callback();
state._args = args;
render();
}
}
function useMemo(callback, args) {
const state = getHookState(currentIndex++);
if (argsChanged(state._args, args)) {
state._args = args;
state._callback = callback;
state.value = callback();
return state.value;
}
return state.value;
}
复制代码
如今用以上 43 行代码实现了一个简易的 React Hooks。
咱们再经过一次总体的流程图来说解完整版的实现。
function Counter() {
const [count, setCount] = useState(0);
const [firstName, setFirstName] = useState("Rudi");
const computed = () => {
return count * 10 - 2;
};
const sum = useMemo(computed, [count]);
useEffect(() => {
console.log("init");
}, []);
return (
<div> <div> {count} * 10 - 2 = {sum} </div> <button onClick={() => setCount(count + 1)}>点击</button> <div>{firstName}</div> <button onClick={() => setFirstName("Fred")}>Fred</button> </div>
);
}
复制代码
将全部的状态都存进闭包中。
改变了第二个状态的value值。
将全部状态依次取出,进行渲染。
经过以上的实现,咱们也能够明白一些 React Hooks 中看似有点奇怪的规定了。例如为何不要在循环、条件判断或者子函数中调用? 由于顺序很重要,咱们将缓存(状态)按必定地顺序压入数组,因此取出上一次状态,也必须以一样的顺序去获取。不然的话,会致使获取不一致的状况。。。固然咱们能够试想一下,若是每一个状态单元,能够有惟一的名字,那么将不会受到这些规定的约束。可是这样会使得开发带来额外的传入参数,就是惟一的名字。也会带来名字冲突等问题,所以采用这样的方式来约定,必定程度上简化了开发者的开发成本,而且也可以消除不一致性。(ps: 若是有人有兴趣,能够实现一版不依赖于顺序,只依赖于名字的,当作小玩具~)
固然真实中的 react 是利用了单链表来代替数组的。略微有些不同,可是本质的思路是一致的,以及 useEffect 是每次渲染完成后运行的。
以上都是站在巨人的肩膀上(有不少优秀的文章,看参考),再加上查看一些源码得出的整个过程。最后,留出一个小问题给你们,那么每次 useEffect
中 return 函数
的逻辑又是怎么样的呢?欢迎评论区说出实现方式~ 若是文章有任何问题,也欢迎在评论区指出~
zh-hans.reactjs.org/docs/hooks-…
zh-hans.reactjs.org/docs/hooks-…