本篇文章参考以下博文
文章目录
前言
最近 React 项目上碰到一个内存泄漏问题,定位了好久,发现是 React 框架存在缺陷,销毁的节点并没有被真的销毁,导致越积越多,造成崩溃现象。在找修改方法的时候,由于出现问题的版本是 16.8 所以就以这个版本号为关键字进行搜索,结果出来的很多都是 React Hooks ,简单看了一下这个新特性,相比于原来的使用方式,直观感受更轻量级,更符合函数式编程的思想,而不是类编程。决定好好研究一下这个 API。预估这会代替现在的编程方式,进而成为主流。
一、函数组件
React 的核心思想是组件化,尽可能的把任何一个模块都写成组件,并且通过类实现。这样的构建思想对于新手来说不太友好,因为和以前我们熟悉的那种结构来说,差别很大,需要一点时间去适应。
其实组件最佳的写法,应该是函数,而不是类,比如 React 原生就支持以下写法。
function sayHello(props) {
return <p>Hello World! My name is {props.name}</p>;
}
虽然上面的写法很符合我们希望的样子,但是里面没有状态,也没有生命周期钩子函数,使用很局限,基本上只能渲染静态数据。
所以 React Hooks 的设计初衷就是可以 完全替代类的函数组件。
二、Hook 钩子
所有钩子的都是为函数引入外部功能,React 默认提供一些常用钩子,同时也支持自己封装钩子。按照惯例,钩子的命名格式是以 use 开头,后面接功能名称。下面是默认提供的四个钩子。
useState()
useContext()
useReducer()
useEffect()
三、useState():状态记录
该钩子替代了原来的state,可以提供给我们用来进行状态管理
import React, { useState } from "react";
export default function ChangeContent() {
const [text, setText] = useState("Bfore");
function handleClick() {
return setText("After");
}
return <button onClick={handleClick}>{text}</button>;
}
上面这种书写格式,重新让我们回归到了,函数式编程的模式当中,一开始先定义个变量,中间是执行方法,最后返回结果。
useState() 函数入参是状态的初始值,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是 text),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是 set 前缀加上状态的变量名(上例是 setText)。
3.1 useState() 原理
该钩子函数会返回当前状态的属性和设置状态的方法,而且当状态改变以后,会更新一遍视图,调用 render() 方法。
let memoizedState;
function useState (initialState) {
memoizedState = memoizedState || initialState
function setState (newState) {
memoizedState = newState
render()
}
return [memoizedState, setState]
}
3.2 多个 state 原理
上面代码在使用一个状态的时候,执行没有问题,但是当需要使用多个状态的时候,只能改变一个状态。例如如下情况。
import React, { useState } from "react";
export default function ChangeContent() {
const [text1, setText1] = useState("button1");
const [text2, setText2] = useState("button2");
function handleClick1() {
return setText1("change1");
}
function handleClick2() {
return setText2("change2");
}
return
<div>
<button onClick={handleClick1}>{text1}</button>
<button onClick={handleClick2}>{text2}</button>
</div>;
}
对于上面这种多个 state 的情况,需要使用数组来保存 state 。
let memoizedStates = []
let index = 0
function useState (initialState) {
memoizedStates[index] = memoizedStates[index] || initialState
let currentIndex = index
function setState (newState) {
memoizedStates[currentIndex] = newState
render()
}
return [memoizedStates[index++], setState]
}
四、useContext():状态共享
原来使用类组件的时候,状态共享,需要我们使用 redux,或者通过父子传值来完成,现在在 Hooks 中,给我们提供了这样一个钩子方法。
const AppContext = React.createContext({});
<AppContext.Provider value={{
data: 'hello world'
}}>
<div className="hooks">
<Title/>
<Content/>
</div>
</AppContext.Provider>
下面是 Title 组件内部
const Title = () => {
const { data } = useContext(AppContext);
return (
<div className="child">
<p>{data}</p>
</div>
);
}
上面方法中,通过 useContext() 钩子函数引入 Context 对象,获取其中的 data 属性。
五、useReducer():状态管理
熟悉 redux 的同学一定对 Reducer 不陌生,在 redux 中,更新状态需要 触发 action ,然后使用 Reducer 函数计算出新的状态,所以 Reducer 的函数形式是 (state, action) => newState 。
hooks 中通过 useReducer() 来实现 Reducer 的功能。
const [state, dispatch] = useReducer(reducer, initialState);
接收 Reducer 和初始 state 作为入参,返回状态当前值和 dispatch 函数。计数器示例如下:
const myReducer = (state, action) => {
switch(action.type) {
case('countUp'):
return {
...state,
count: state.count + 1
}
default:
return state;
}
}
组件内部代码。
function App() {
const [state, dispatch] = useReducer(myReducer, { count: 0 });
return (
<div className="App">
<button onClick={() => dispatch({ type: 'countUp' })}>
+1
</button>
<p>Count: {state.count}</p>
</div>
);
}
当只需要共享状态和 Reducer 函数的时候,可以代替 redux 。但是当需要 中间件(middleware)和 时间旅行(time travel)的时候,还是需要使用 redux,配置 redux-thunk 和 redux-devtools 。
六、useEffect():副作用钩子
useEffect() 用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在 componentDidMount 里面的代码,现在可以放在 useEffect() ,用法如下。
useEffect(() => {
// Async Action
}, [dependencies])
上面用法中,第一部分的函数中,用来放异步代码。第二部分的数组,是前面函数的依赖项,只有数组中的数据变化时,才会执行 useEffect() 。如果省略第二个参数,那只要组件渲染,就会执行 useEffect() 。
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch('http://127.0.0.235/Loginfo', {
method: 'POST',
body: personId,
headers: {
'Content-Type': 'application/json',
'Authorization': window['token']
}
}).then((response) => {
if (response.data.ErrCode === 0) {
setPerson(response.data);
setLoading(false);
}
})
}, [personId])
if (loading === true) {
return <p>Loading ...</p>
}
return (
<div>
<p>Name: {person.name}</p>
<p>Height: {person.height}</p>
<p>Weight: {person.mass}</p>
</div>
);
}
七、封装自定义 Hooks
上面的方法封装一下,就可以成为我们自定义的 Hook 。
const usePerson = (personId) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch('http://127.0.0.235/Loginfo', {
method: 'POST',
body: personId,
headers: {
'Content-Type': 'application/json',
'Authorization': window['token']
}
}).then((response) => {
if (response.data.ErrCode === 0) {
setPerson(response.data);
setLoading(false);
}
})
}, [personId]);
return [loading, person];
};
使用自定义 Hook 方法如下:
const Person = ({ personId }) => {
const [loading, person] = usePerson(personId);
if (loading === true) {
return <p>Loading ...</p>;
}
return (
<div>
<p>Name: {person.name}</p>
<p>Height: {person.height}</p>
<p>Weight: {person.mass}</p>
</div>
);
};