React Hooks (一文看懂四个常用的钩子)
上次看了阮一峰老师的文章,写的很通俗易懂,我在这简单给大家梳理一下
我们在开发中常用的就以下四个Hooks。
- useState()
- useContext()
- useReducer()
- useEffect()
1、useState():状态钩子
useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
const [num,setNum] = useState<number>(1)
这一句代码是用白话说就是有个num变量,必须是number类型的(<>尖括号里边的是TYpescript限制类型的写法),初始值为一个数字1,然后setNum就是一个方法,通过一个前缀set就能看出来。setNum(10),通过set了一下,num的值就为10了。
<div onClick= {()=>setNum(10)}>
再看一个例子
import React, { useState } from "react";
export default function Button() {
const [buttonText, setButtonText] = useState("Click me, please");
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
上面代码中,Button 组件是一个函数,内部使用useState()钩子引入状态。
useState()这个函数接受状态的初始值,作为参数,上例的初始值为按钮的文字。该函数返回一个数组,数组的第一个成员是一个变量(上例是buttonText),指向状态的当前值。第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名(上例是setButtonText)。
2、useContext():共享状态钩子
如果一个父组件有两个子组件,现在这两个子组件之间需要共享状态,就可以使用useContext
现在有两个组件 Navbar 和 Messages,我们希望它们之间共享状态。
<div className="App">
<Navbar/>
<Messages/>
</div>
第一步就需要使用React Context API,在组件外部建一个Context。
const AppContext = React.creactContext({})
组件封装代码如下
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
上面代码中,AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享。
在组件 Navbar 和 Messages中,我们可以通过使用const {username} = useContext(AppContext)
,拿到父组件的username了
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
3、useReducer():action 钩子
React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux。
Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState
。
useReducers()
钩子用来引入 Reducer 功能。
const [state, dispatch] = useReducer(reducer, initialState);
上面是**useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送**action 的dispatch
函数。
下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。
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>
);
}
4、useEffect():副作用钩子
什么是副作用?
看到这里,你可能会产生一个疑问:如果纯函数只能进行数据计算,那些不涉及计算的操作(比如生成日志、储存数据、改变应用状态等等)应该写在哪里呢?
函数式编程将那些跟数据计算无关的操作,都称为 “副效应” (side effect) 。如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数。
纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应。
useEffect()
本身是一个函数,由 React 框架提供,在函数组件内部调用即可。
举例来说,我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变网页标题这个操作,就是组件的副效应,必须通过useEffect()
来实现。
import React, { useEffect } from 'react';
function Welcome(props) {
useEffect(() => {
document.title = '加载完成';
});
return <h1>Hello, {props.name}</h1>;
}
上面例子中,useEffect()
的参数是一个函数,它就是所要完成的副效应(改变网页标题)。组件加载以后,React 就会执行这个函数。
useEffect()
的作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行
当然useEffect
也有第二个参数,第二个参数是UseEffect的依赖项。
有时候,我们不希望useEffect()
每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。
function Welcome(props) {
useEffect(() => {
document.title = `Hello, ${props.name}`;
}, [props.name]);
return <h1>Hello, {props.name}</h1>;
}
上面例子中,useEffect()的第二个参数是一个数组,指定了第一个参数(副效应函数)的依赖项(props.name
)。只有该变量发生变化时,副效应函数才会执行。
如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。
只要是副效应,都可以使用
useEffect()
引入。它的常见用途有下面几种。
- 获取数据(data fetching)
- 事件监听或订阅(setting up a subscription)
- 改变DOM(changing the DOM)
- 输出日志(logging)
注意:使用useEffect()
时,有一点需要注意。如果有多个副效应,应该调用多个useEffect()
,而不应该合并写在一起。