不是说非要学这个Hooks,而是因为这个用起来真的很符合真香定理,没法不学啊~真的是谁用谁知道
正文开始:
React那么多生命周期的钩子函数到底该用哪个?组件还分有状态和无状态,那我想让函数组件也有自己的状态怎么办?难道函数组件就不配拥有状态吗?每次写个方法还得手动改变this指向?oh my god ! 有了Hooks,这些都再也不是问题了~
Hook顾名思义就是钩子的意思。在函数组件中把React的状态和生命周期等这些特性钩入进入,这就是React的Hook,那么 React Hooks 相比于从前的类组件到底有哪些好处呢?
- 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
- 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现
useState 保存组件状态
在类组件中,我们使用 this.state
来保存组件状态,并对其修改触发组件重新渲染。比如下面这个简单的计数器组件,很好诠释了类组件如何运行:
import React from "react";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
const { count } = this.state;
return (
<div>
Count: {count}
<button onClick={() => this.setState({ count: count + 1 })}>+</button>
<button onClick={() => this.setState({ count: count - 1 })}>-</button>
</div>
);
}
}
一个简单的计数器组件就完成了,而在函数组件中,由于没有 this 这个黑魔法,React 通过 useState 来帮我们保存组件的状态
以前的函数式组件被成为纯函数组件或者无状态组件,是只能接受父组件传来的props并且只能做展示功能,不能使用state也没有生命周期。
现在State Hook 可以让函数式组件使用状态。
useState是React的一个Hook,它是一个方法,可以传入值作为state的默认值,返回一个数组,数组的第一项是对应的状态(默认值会赋予状态),数组的第二项是更新状态的函数
import React, { useState } from "react";
function App() {
const [countObj, setCountObj] = useState({
count: 0
});
return (
<div className="App">
Count: {obj.count}
<button onClick={() => setCountObj({ ...countObj, count: countObj.count + 1 })}>+</button>
<button onClick={() => setCountObj({ ...countObj, count: countObj.count - 1 })}>-</button>
</div>
);
}
有个 useState 后,函数组件也可以拥有自己的状态了
useEffect 处理副作用
函数组件能保存状态,但是却无法执行异步请求等副作用的操作,所以 React 提供了 useEffect 来帮助开发者处理函数组件的副作用操作
先看看原来的类组件是怎么处理的:
import React, { Component } from "react";
class App extends Component {
state = {
count: 1
};
componentDidMount() {
const { count } = this.state;
document.title = "componentDidMount" + count;
this.timer = setInterval(() => {
this.setState(({ count }) => ({
count: count + 1
}));
}, 1000);
}
componentDidUpdate() {
const { count } = this.state;
document.title = "componentDidMount" + count;
}
componentWillUnmount() {
document.title = "componentWillUnmount";
clearInterval(this.timer);
}
render() {
const { count } = this.state;
return (
<div>
Count:{count}
<button onClick={() => clearInterval(this.timer)}>clear</button>
</div>
);
}
}
我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。
同时,由于前文所说hooks可以反复多次使用,相互独立。所以我们合理的做法是,给每一个副作用一个单独的useEffect钩子。这样一来,这些副作用不再一股脑堆在生命周期钩子里,代码变得更加清晰
在上边的例子中,组件每隔一秒更新组件状态,并且每次触发更新都会触发 document.title 的更新(副作用),而在组件卸载时修改 document.title,接下来我们用useEffect来实实现它。
import React, { useState, useEffect } from "react";
let timer = null;
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = "componentDidMount" + count;
},[count]);
useEffect(() => {
timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => {
document.title = "componentWillUnmount";
clearInterval(timer);
};
}, []);
return (
<div>
Count: {count}
<button onClick={() => clearInterval(timer)}>clear</button>
</div>
);
}
我们使用 useEffect 重写了上面的例子,useEffect 第一个参数传递函数,可以用来做一些副作用比如异步请求,修改外部参数等行为,而第二个参数是个数组,如果数组中的值才会触发 useEffect 第一个参数中的函数。返回值(如果有)则在组件销毁或者调用函数前调用。
- 比如第一个 useEffect 中,理解起来就是一旦 count 值发生改变,则修改 documen.title 值
- 而第二个 useEffect 中数组没有传值,代表不监听任何参数变化,即只有在组件初始化或销毁的时候才会触发,用来代替 componentDidMount 和 componentWillUnmount;
useContext 祖孙传值
useContext 是 React 帮你封装好的,用来处理多层级传递数据的方式,在以前组件树种,跨层级祖先组件想要给孙子组件传递数据的时候,除了一层层 props 往下透传之外,还可以使用useContext来解决爷孙组件的传值问题,新的Context API使用订阅发布者模式方式实现在爷孙组件中传值
const { Provider, Consumer } = React.createContext(null);
function Bar() {
return <Consumer>{value => <div>{value}</div>}</Consumer>;
}
function Foo() {
return <Bar />;
}
function App() {
return (
<Provider value={"hello context"}>
<Foo />
</Provider>
);
}
通过 React createContext 的语法,在 APP 组件中可以跨过 Foo 组件给 Bar 传递数据。而在 React Hooks 中,我们可以使用 useContext 进行改造。
const Context = React.createContext("hello");
function Bar() {
const val = useContext(Context); // 使用useContext直接取值
return <div>{val}</div>;
}
function Foo() {
return <Bar />;
}
function App() {
return (
<Context.Provider value={"hello react"}>
<Foo />
</Context.Provider>
);
}
传递给 useContext 的是 context 而不是 consumer,返回值即是想要透传的数据了。用法很简单,使用 useContext 可以解决 Consumer 多状态嵌套的问题。
而使用 useContext 则变得十分简洁,可读性更强且不会增加组件树深度。
useReducer
看到useReducer,肯定会想到Redux,没错它和Redux的工作方式是一样的。useReducer的出现是useState的替代方案,能够让我们更好的管理状态,但是缺点也是存在的,就是无法实现redux的中间件
import React, { useReducer } from "react";
const initialState = {
count: 0
};
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - action.payload };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "increment", payload: 5 })}>
+
</button>
<button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
-
</button>
</>
);
}
用法跟 Redux 基本上是一致的,如果你学过redux,对于useReducer基本没有学习门槛
总结
React Hooks说到底绝对是一种趋势,因为Hooks用起来实在是让人欲罢不能,不需要考虑那么多,用起来更加的方便快捷,更像是回归了js的本质,也不会增加嵌套层级,何乐而不为呢?