hooks
# React Hooks原理
作者:在剥我的壳
链接:https://juejin.cn/post/6982755396976902158
- 函数组件执行函数
- 执行函数组件 renderWithHooks
- 改变 ReactCurrentDispatcher 对象
- 初始化hooks
- 通过 mountWorkInProgressHook 生成hooks链表
- 通过 mountState 来初始化 useState
- 通过 dispatchAction 来控制无状态组件的更新
- 通过 mountEffect 初始化 useEffect
- 通过 mountMemo 初始化 useMemo
- 通过 mountRef 初始化 useRef
- 更新hooks
- 通过 updateWorkInProgressHook 找到对应的 hooks 更新 hooks 链表
- 通过 updateState 得到最新的 state
- 通过 updateEffect 更新 updateQueue
- 通过 updateMemo 判断 deps,获取or更新缓存值
- 通过 update 获取 ref 对象
# 当你使用了hooks( 例如 useState)时,发生了什么?
我们去看 useState
的源码
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
exports.useState = useState;
2
3
4
5
6
useState(initialState)
等价于 dispatcher.useState(initialState)
。 dispatcher
从中文意思上是 调度员 的意思。 也就是说你调用 useState
的时候只是通知了调度员去调度真正的 useState
。
# 那调度员 dispatcher 又是什么?
上源码:
function resolveDispatcher() {
var dispatcher = ReactCurrentDispatcher.current;
if (!(dispatcher !== null)) {
{
throw Error( "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem." );
}
}
return dispatcher;
}
// ReactCurrentDispatcher是一个对象
/**
* Keeps track of the current dispatcher.
*/
var ReactCurrentDispatcher = {
/**
* @internal
* @type {ReactComponent}
*/
current: null
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dispatcher
是从 ReactCurrentDispatcher
身上来。我们来把这个此分析一下,react 当前的(current)调度员(Dispatcher)。
也就是说,到这里 Dispatcher
就已经安排好了。
# 钩子hooks概念
作者:小胖墩er
函数式组件的方法里面是没有this的
// 1、创建函数式组件
function DemoCompoent () {
console.log(this); // undefined 因为babel编译后开启了严格模式
return <h1>我是函数式组件(适用于【简单的组件】定定义)</h1>
}
// 2、渲染组件到页面
ReactDOM.render(<DemoCompoent/>, document.getElementById('test'));
/*
执行 ReactDOM.render 之后发生了什么?
React 解析组件标签,找到了 DemoCompoent 组件;
发现组件是使用函数式定义的,随后调用该函数,将返回的虚拟 DOM 转换成真实 DOM, 随后呈现在页面中。
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
函数式组件的基础hooks(为了在不使用类组件的情况下,使用state与React其它相关操作)
# 一:什么是钩子(hooks)
- 消息处理的一种方法,用来监视指定程序;
- 函数组件中需要处理副作用,可以用钩子把外部代码钩进来
- 常用钩子:useState,useEffect,useContext,useReducer
- hooks一律使用use前缀命名:useXxx
# 二:Hooks的本质
一类特殊的函数,为你的函数式组件(function component)注入了特殊的功能
# 三:React为什么要创造Hooks这个概念呢?
概念:
我们都知道React的核心思想就是组件化(组件化的最大好处:彼此独立,可以复用)
1、有些类组件冗长而且复杂,难以复用
解决方案:
无状态组件与HOC(高阶组件),但还是存在诸多问题
(1) 无状态组件字面意思就是没有state,当组件没有了状态state,它就是一个纯函数,会变得非常简单,所有的页面仅仅依赖于props的注入,但是无状态组件最致命的是它没有生命周期,没有副作用,访问不了api,进行不了异步的数据更新。
(2) 高阶组件则走向了另外一种极端,它不管你原先的组件有多么的复杂,它会直接在组件外再套一层组件,通过组件一层一层的嵌套,来达到组件复用的目的,这种方法的确解决了复杂组件的复用问题,但是更加加深了组件的复杂性,还会出现类似回调地狱的dom结构
# 四:Hooks横空出世
概念:
- Hooks目的就是为了给函数式组件加上状态
- 生命周期函数会同时处理多项任务,发起ajax、跟踪数据状态、绑定事件监听、绑定事件监听
- 函数式组件则轻量化很多,使用Hooks钩子来钩入组件的状态
# 五:Hooks代表了React架构的一次重大变革
- 我们不再需要类组件了
- 不会再有this,不会再有binding、甚至有可能取代redux
- 简化了代码、减少了模板,降低了学习的难度
# 常见的hooks
作者:小胖墩er
# 一: 状态钩子:useState( ) ;
const [count , setCount] = useState(0);
- React自带的一个hook函数,声明组件状态;
- 参数可以设置state的初始值(initial state)
- 返回值是一个只有两个元素的数组:[ 状态 , 状态更新函数]
其次,useState是可以多次调用的,所以我们完全可以这样写:
function ExampleWithManyStates() {
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
2
3
4
5
- 初始值没有规定一定要是string/number/boolean这种简单数据类型,它完全可以接收对象或者数组作为参数。
- useState无论调用多少次,相互之间是独立的。
例子
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 更新文档的标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
我们对比着看一下,如果没有hooks,我们会怎么写?
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
// 组件挂载完毕
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
// 组件更新完毕
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。
相对前文所说hooks可以反复多次使用,相互独立。所以我们合理的做法是,给每一个副作用一个单独的useEffect钩子。这样一来,这些副作用不再一股脑堆在生命周期钩子里,代码变得更加清晰。
怎么跳过一些不必要的副作用函数?
我们只需要给useEffect传第二个参数即可。用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(第一个参数)。
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 只有当count的值发生变化时,才会重新执行`document.title`这一句
2
3
当我们第二个参数传一个空数组[]时,其实就相当于只在首次渲染的时候执行。也就是componentDidMount加componentWillUnmount的模式。不过这种用法可能带来bug,少用。
# 三:还有哪些自带的Effect Hooks?
- useContext 用来跨组件的数据传递
- useReducer 用来管理全局的状态
- useCallback 用来处理回调的副作用
- useRef 用来返回引用的对象,而这个引用对象在整个组件的生命周期中都会保持不变
- useLayoutEffect 与useEffect非常相似,也用来处理副作用,它会在所有的dom元素变更了之后同步调用,读取dom布局,并同步触发,重新渲染
- useDebugValue 可以在React开发者工具中显示自定义的Hooks标签方便开发
# 函数式组件与类式组件之间的对比?
先来看看类式组件
import React, { Component } from 'react'
export default class ClassCom extends Component {
state = {
count: 1
}
clickHandel = () => {
const {count} = this.state;
this.setState({
count: count +1
})
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.clickHandel}>
Click me
</button>
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
使用了hooks的函数式组件
import React, { useState } from 'react'
export default function FuncCom() {
const [count, setCount] = useState(0);
function clickHandel() {
setCount(count + 1)
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={clickHandel}>
Click me
</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
我们可以看到使用了hooks,组件中根本用不到this。就可以使用状态(state)。
因为我们的状态count就是一个单纯的变量而已,我们再也不需要写成{this.state.count}
这样了。
# 总结
作者:小胖墩er
在函数式组件中, 使用hooks语法模拟状态数据的步骤
- 从react中导入语法函数setState
import React, { useState } from "react"
- 在函数式组件中, 使用setState创建状态数据
const [name, setName] = useState("名字")
- 在组件模板中, 直接调用状态名即可
自定义状态name: {name}-{age}
- 使用setState函数返回的更新函数修改状态值, 参数是新值,修改后自动刷新界面
setName("李四")
useEffect
// useEffect() 这个函数可以用来模拟组件的生命周期函数, 他有两个参数,
// 第一个参数是回调函数, 当组件初始化完成和状态更新时调用
// 第二个参数是一个数组,可选, 数组中是状态名, 指定那些状态值更新会触发回调函数
useEffect(()=>{
// 如果不加第二个参数, 初始化时调用, 任何状态更新都会调用
console.log("组件初始化,或有状态更新ComponentWillUpdate")
})
useEffect(()=>{
// 如果第二个参数是空数组, 则只在初始化时调用,状态更新时不会调用
console.log("ComponentDidMount")
}, [])
useEffect(()=>{
// 如果第二个参数数组中有状态名, 则只会在数组中的状态更新时调用
console.log("NameWillUpdate")
}, [name])
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18