前面在学vue的时候写了一篇自定义指令从入门到不离不弃,现在写vue已经有四五个月了,发现对自己之前用的react倒有点生疏了,也就才几个月没用,写代码这事真的是不能停,所以趁着还没完全忘记,赶紧捡起来自己的老本行,来一篇react hooks从入门到不离不弃。talk is cheap, show me the code。
什么是hooks
- hooks 是 React 16.8 新增的特性,丰富扩展了原有函数组件的功能,让函数组件也有了类组件的一些特性。
hooks解决了什么问题
类组件的缺点
1. this指向问题
- 例如每次声明函数都需要手动的去绑定 this ,想要获取 state 和 props 还需要通过 this.state.xxx 和 this.props.xxx 来获取,代码不够简洁
2. 代码复杂,难以组织
- 数据获取时,需要在组件挂载 (componentDidMount) 和组件更新 (componentDidUpdate) 阶段分别去获取,不够统一
- 需要事件监听时,需要在需要在组件挂载 (componentDidMount) 时注册事件,在和组件卸载 (componentWillUnmount) 时卸载事件,这种分散的写法很容易遗漏导致忘记卸载事件。
3. 组件之间状态复用困难
- 类组件中的状态都是通过 state 定义在组件内部没办法抽离的,如果其他组件也需要用到相同的 state 则需要重定义一份
- react 本身为单向数据流,状态只能从父组件传递到子组件中,如果需要子组件修改父组件的状态,则需要用到 Hoc(高阶组件) 或者 render props(渲染属性) 来解决,这样都会造成组件变得复杂
hooks如何解决的
1. this指向问题
- 可以把函数组件理解成普通的函数,我们在函数中定义的变量和函数是可以直接访问的,而不需要用 this 去访问,因此也不用关心this的指向
2.代码复杂,难以组织
- react 中内置的 useEffect 钩子,可以实现类组件中的 componentDidMount、componentDidUpdate、componentWillUnmount 三个生命周期的作用,所以我们就可以将事件注册和卸载以及数据获取统一起来管理,解决之前存在的分散的问题。
3. 组件之间状态复用困难
- react hook 除了内置的几个以外,是支持自定义 hook 的,我们可以将一些公用的状态处理逻辑单独封装成一个 hook,在多个组件中可以共用,这样就解决了类组件中的状态复用的问题。
除了以上的优点外,hook 还有其他的优点,可以用来实现一些类组件实现不了的功能,比如可以用 hooks 实现一个简单的全局数据管理的功能,代替 redux。
常见hook有哪些
接下来我们就来一个一个地讲解react中常用的一些hook
useState
useState 是用来解决函数组件中不能定义自己的状态的问题,useState 可以传递一个参数,做为状态的初始值,返回一个数组,数组的第一个元素是返回的状态变量,第二个是修改状态变量的函数。
const [state, setState] = useState(initalState); // 初始化,state可以任意命名
// ...
setState(newState); // 修改state的值
在类组件中是通过 this.setState 来修改类组件中的状态值的,函数组件中则通过 useState 来修改,不同的是 this.setState 是合并修改,但是 useState 中则是直接替换。
示例:
import { useState } from 'react';
function Demo() {
const [count, setCount] = useState(0);
const add = () => {
setCount(count + 1);
}
return (
<div>
<button onClick={add}>+1</button>
<p>{`count: ${count}`}</p>
</div>
);
}
export default Demo;
如果这段代码用类组件写的话该如何写呢?
import React from 'react';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
this.add = this.add.bind(this);
}
add() {
this.setState({
count: this.state.count + 1
})
}
render () {
return (
<div>
<button onClick={this.add}>add1</button>
<p>{`count: ${this.state.count}`}</p>
</div>
);
}
}
export default Demo;
高级用法
-
思考:这样可以实现对count加3的操作吗
import { useState } from 'react'; function Demo() { const [count, setCount] = useState(0); const add = () => { for (let i = 0; i < 3; i ++) { setCount(count + 1); } // for (let i = 0; i < 3; i ++) { // setCount(prev => prev + 1); // } } return ( <div> <button onClick={add}>+3</button> <p>{`count: ${count}`}</p> </div> ); } export default Demo;
答案:不能实现
原因:在类组件中 this.setState 是异步执行的,同样 useState 修改状态也是异步的,虽然 setCount 执行了三次,但是因为每次执行并不是立即生效的,所以每次拿到的 count 的值其实是一样的,都是初始值,所以其实相当于是就执行了1次。
-
思考:alert的时候能拿到最新的值吗
import { useState } from 'react'; function Demo(){ let [count, setCount] = useState(0); const alertCount = () => { setTimeout(() => { alert(count); }, 3000); } const add = () => { setCount(count + 1); } return ( <div> <button onClick={add}>+1</button> <button onClick={alertCount}>alert</button> <p>{`count: ${count}`}</p> </div> ) }
答案:不能
原因:函数组件在每次渲染的时候都会形成一个闭包,都有自己的 state 和 props 以及 事件处理函数,每次点击 add 时都会重新渲染,这个时候执行 alert 拿到的是那一次渲染的值,之后再更新 count 是不会对当时的状态有影响的。
useEffect
我们写的很多组件都会产生很多的副作用,比如 ajax 请求,因为要与服务端通信,可能会更新缓存,保存日志等,还有操作 dom 也算是副作用,这些副作用在类组件中都是放在 componentDidMount,componentDidUpdate 中执行的,在 hook 中可以用 useEffect 来处理副作用。
useEffect 可以传入2个参数,第1个参数为我们定义的执行函数、第2个参数是依赖关系(可选参数)。若一个函数组件中定义了多个useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。
useEffect(() => {
// 此处编写 组件挂载之后和组件重新渲染之后执行的代码
...
return () => {
// 此处编写 组件即将被卸载前执行的代码
...
}
}, [dep1, dep2 ...]); // 依赖数组
- 第一个参数中的代码是组件挂载和更新就会执行的代码
- return 出去的代码会在组件卸载时才会执行
- 依赖数组不是必填项,如果不传则每次渲染都会去执行,传值的话在依赖项发生改变时函数中的代码才会执行,如果传空数组则会在组件第一次挂载才会执行
示例:
import { useState, useEffect } from 'react';
function Demo() {
const [count, setCount] = useState(0);
const [a, setA] = useState(0);
useEffect(() => { // 实现componentDidMount、componentDidUpdate、componentWillUnmount
console.log('---effect 执行---');
document.title = `被点击了${count}次`;
})
const add = () => {
setCount(count + 1);
}
const addA = () => {
setA(a + 1);
}
return (
<div>
<button onClick={add}>add</button>
<button onClick={addA}>addA</button>
<p>{`count: ${count}`}</p>
<p>{`a: ${a}`}</p>
</div>
);
}
export default Demo;
清除副作用
import { useState, useEffect } from 'react';
function Demo() {
const [count, setCount] = useState(0);
const [a, setA] = useState(a);
useEffect(() => {
// 组件挂载时注册计数器
let timer = setInterval(() => {
setA(a+1)
}, 1000);
// 组件卸载时卸载计时器
return () => {
clearInterval(timer);
}
}, []);
useEffect(() => {
// 修改页面标题
document.title = `被点击了${count}次`;
}, [count]);
const addCount = () => {
setCount(count + 1);
}
return (
<div>
<button onClick={addCount}>addCount</button>
<p>{`count: ${count}`}</p>
<p>{`a: ${a}`}</p>
</div>
);
}
export default Component;
useLayoutEffect
useLayoutEffect 使用方法、所传参数和 useEffect 完全相同。大多数情况下将 useEffect 替换成 useLayoutEffect 完全看不出区别,那两个函数的区别在哪呢?
示例:
import { useState, useEffect, useLayoutEffect } from 'react'
function Demo() {
const [str, setStr] = useState('hello world');
useEffect(() => {
let i = 0;
while(i < 100000000) {
i ++;
}
setStr('world hello')
}, []);
useLayoutEffect(() => {
let i = 0;
while(i < 100000000) {
i ++;
}
setStr('world hello')
}, []);
return (
<div>
<p>{`str: ${str}`}</p>
</div>
)
}
可以发现使用 useEffect 时,页面挂载会出现闪烁,而使用 useLayoutEffect 时页面没有闪烁,是因为 useEffect 是在页面渲染完成后再去更新 str 的,所以会出现短暂的闪烁,而 useLayoutEffect 是在页面还没有渲染时就将 str 给更新了,所以没有出现闪烁。为什么会出现这种情况呢?
原因:
- useEffect 是在 dom 渲染到页面后才会去执行,useLayoutEffect 则是在 dom 变化渲染到页面之前执行的
- 如果有操作 dom 的逻辑尽量放在 useLayoutEffect 中,因为如果放在 useEffect 中的话会重新触发浏览器进行回流,重绘
- useEffect 能覆盖大部分场景,因为 useLayoutEffect 会阻塞渲染,所以需要小心的使用
useMemo
useMemo 是为了减少组件重新渲染时不必要的函数计算,可以用来做性能优化
const memoizedValue = useMemo(() => {
// 计算逻辑
...
// return res;
}, [a, b]);
useMemo 可以传入2个参数,第1个参数为函数,用来进行一些计算,第2个参数是依赖关系(可选参数),返回值为第一个函数 return 出去的值,只有在依赖项发生变化时才会重新执行计算函数进行计算,如果不传依赖项,每次组件渲染都会重新进行计算。
示例:
import { useState, useMemo } from 'react'
function Demo() {
const [num, setNum] = useState(2022);
const [count, setCount] = useState(0);
const addCount = () => {
// count更新并不会出发 useMemo 重新计算
setCount(count + 1);
}
const addNum = () => {
// num 更新并不会出发 useMemo 重新计算
setNum(num + 100);
}
const total = useMemo(() => {
console.log('---求和---');
// 求和计算
let temp = 0;
for(let i = num; i > 0; i--) {
temp += i;
}
return temp;
}, [num]);
return (
<div>
<button onClick={addCount}>addCount</button>
<button onClick={addNum}>addNum</button>
<p>{`count: ${count}`}</p>
<p>{`num: ${num}`}</p>
<p>{`total: ${total}`}</p>
</div>
)
}
-
点击修改 count 的值会引发组件重新渲染,但是 total 对应的计算函数却不需要重新计算一遍。
-
点击修改num的值,total 对应的计算函数肯定会重新执行一遍,因为num是该计算函数的依赖。
useCallback
返回一个缓存的回调函数。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b]
);
useCallback 的用法和 useMemo 完全一样,useMemo 返回的是计算函数 return 出去的值,而 useCallback 可以理解成返回的是那个计算函数。
示例:
import { useState, useCallback } from 'react'
function Demo() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(0);
const clickHanlder = () => {
setCount(count + 1);
};
// const clickHanlder = useCallback(() => {
// setCount(count + 1);
// }, [count]);
const addNum = () => {
setNum(num + 1);
}
return (
<div>
<Button clickHanlder={clickHanlder}></Button>
<button onClick={addNum}>addNum</button>
</div>
)
}
const Button = (props) => {
const { clickHanlder } = props;
console.log('---render---');
return (
<button onClick={clickHanlder}>{'addCount'}</button>
);
}
React.memo()
类组件中有 shouldComponentUpdate 和 PureComponent 来避免子组件做不必要的渲染,shouldComponentUpdate 接收两个参数,nextProps 和 nextState,可以用来跟 this.props 和 this.state 进行比较,返回 true 表示需要更新,返回 false 表示可以跳过更新。PureComponent 则会自己对 props 和 state 做一个浅比较来决定要不要更新。
函数组件中的 React.memo() 也有类似的功能,它和 PureComponent 类似,但是只适用于函数组件,默认情况下仅对 props 进行一个浅比较来决定要不要更新,复杂情况下支持自己写对比的逻辑。
function Demo(props) {
// ...
}
function compare(prevProps, nextProps) {
// 自己写对比逻辑,返回 true 更新,false 跳过更新
// return false
}
export default React.memo(Demo, compare)
const Button = React.memo((props) => {
const { label, clickHanlder } = props;
console.log('---render---');
return (
<button onClick={clickHanlder}>{label}</button>
);
});
useRef
useRef 可以帮助我们获取 dom 和 react 组件实例,类组件中的 React.createRef() 也有相同的功能。
const xxxRef = useRef(initialValue);
// 使用 xxxRef.current 获取引用的值
示例:
import { useState, useRef } from 'react'
function Demo() {
const inputRef = useRef();
const handleFocus = () => {
// document.getElementById('my-input').focus();
inputRef.current.value = 'focus';
inputRef.current.foucs();
}
const handleBlur = () => {
// document.getElementById('my-input').blur();
inputRef.current.value = 'blur';
inputRef.current.blur();
}
return (
<div>
<input ref={inputRef} id={'my-input'} />
<button onClick={handleFocus}>focus</button>
<button onClick={handleBlur}>blur</button>
</div>
)
}
除了用 useRef 获取组件实例,还可以用来存储变量的值,但是需要注意的一点是,修改 .current 的值不会触发组件的重新渲染,请看下面示例:
import { useState, useRef } from 'react'
function Demo() {
const countRef = useRef(0);
const [num, setNum] = useState(0);
const addCount = () => {
// 使用 useRef 去更新值并不会出发组件渲染
countRef.current = countRef.current + 1;
}
const alertCount = () => {
setTimeout(() => {
alert(countRef.current);
}, 3000);
}
const addNum = () => {
// 使用 useState 去更新会触发组件渲染
setNum(num + 1);
}
return (
<div>
<button onClick={addCount}>addCount</button>
<button onClick={addNum}>addNum</button>
<button onClick={alertCount}>alert</button>
<p>{`count: ${countRef.current}`}</p>
<p>{`num: ${num}`}</p>
</div>
)
}
forwardRef
思考下我们在父组件中想给子组件传递 ref 可以实现吗?
import { useRef, forwardRef } from 'react';
function Child(props) {
const { ref } = props; // 这样是不可以的
return (
<input ref={ref} />
);
}
function Parent() {
const childRef = useRef();
const onFocus = () => {
childRef.current.focus();
}
return (
<div>
<Child ref={childRef} />
<button onClick={onFocus}>focus</button>
</div>
);
}
forwardRef 可以在父组件中操作子组件的 ref 对象,并且将 ref 对象作为一个参数传递给了子组件。
useImperativeHandle
React 中的数据流是单向的,父组件可以将函数和状态传递给子组件,这样子组件内就可以使用父组件中的变量和函数了,如果我们想在父组件中使用子组件内定义的函数和状态呢?这就需要用到 useImperativeHandle 这个 hook 了,使用 useImperativeHandle 的时候还需要结合上面讲的 useRef 和 forwardRef 来结合使用,具体使用步骤如下:
- 在父组件中使用 useRef 创建 ref 引用变量
- 使用 forwardRef 将创建的 ref 引用传递到子组件中去
- 将子组件中的函数和状态通过 useImperativeHandle 挂载到传递过来的 ref 对象上
// ref 为父组件传过来的 ref 引用
useImperativeHandle(ref, () => {
// return出去的属性都挂载在了父组件传过来的ref对象上,若父组件需要调用子组件内的 xxx函数,则通过 ref.current.xxx()调用
return {
xxx: xxx
}
}, [deps]) // deps 为依赖数组,可选项
示例:
import { forwardRef, useState, useImperativeHandle, useRef } from 'react';
function Child(props, ref) {
const [count, setCount] = useState(0);
useImperativeHandle(ref, () => {
return {
addCount: () => {
setCount(count + 1);
}
}
})
return (
<div>
<p>{`count: ${count}`}</p>
</div>
);
}
Child = forwardRef(Child);
function Parent() {
const childRef = useRef();
return (
<Child ref={childRef} />
<button
onClick={() => {
childRef.current.addCount();
}}
>add</button>
);
}
useContext
在 React 中传递属性只能一层一层传,如果组件结构比较复杂,层级比较深的时候,数据传递起来就比较麻烦,可能会经过很多次的传递才能将属性传递到目标组件中,那么有没有一种可以在全局进行状态共享的实现方法呢?useContext 就是为了解决这个问题的,可以实现不必层层传递就能共享状态的功能。
示例:
// context.js
import React from 'react';
// 全局注册
const UserContext = React.createContext({ userName: '张三', age: 24 });
export default UserContext;
// Parent.js
import React from 'react';
import UserContext from './context';
function Parent() {
return (
<UserContext.Provider value={{ name: '李四', age: 28 }}>
<div>
<Child />
</div>
</UserContext.Provider>
);
}
// Child.js
function Child() {
// Child 组件没有用到 context 中的状态,不用做任何处理
return (
<div>
<GrandSon />
</div>
);
}
// GrandSon.js
import UserContext from './context';
import { useContext } from 'react';
function GrandSon() {
// 用 useContext 引入传递的状态
const userContext = useContext(UserContext);
return (
<div>
<p>{`name: ${userContext.name}`}</p>
<p>{`age: ${userContext.age}`}</p>
</div>
);
}
如果全局有多个共享的 context 在子组件中如何使用呢?请看下面示例
import React, { useContext } from 'react'
const UserContext = React.createContext();
const JobContext = React.createContext();
function Parent() {
return (
<UserContext.Provider value={{ name: '张三' }}>
<JobContext.Provider value={{ position: '前端开发' }}>
<Child />
</JobContext.Provider>
</UserContext.Provider>
)
}
function Child() {
const user = useContext(UserContext);
const job = useContext(JobContext);
return (
<div>
<p>{`name: ${user.name}`}</p>
<p>{`job: ${job.position}`}</p>
</div>
)
}
export default AppComponent;
useReducer
useReducer 也是用来实现状态管理的 hook,useState 就是基于 useReducer 实现的,useReducer 可以实现比 useState 更复杂的状态管理逻辑。接下来就看看如何去使用 useReducer 的。
import React, { useReducer } from 'react';
// 1.需要有一个 reducer 函数,第一个参数为之前的状态,第二个参数为行为信息
function reducer(state, action) {
switch (action) {
case 'add':
return state + 1;
case 'minus':
return state - 1;
default:
return state;
}
}
function Demo() {
// 2.引入useReducer,第一个参数时上面定义的reducer,第二个参数时初始值
// 3.返回为一个数组,第一项为状态值,第二项为一个 dispatch 函数,用来修改状态值
const [count, setCount] = useReducer(reducer, 0);
// const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => { setCount({'add'}) }} >add</button> |
<button onClick={() => { dispatch('minus') }} >minus</button> |
<button onClick={() => { dispatch('unknown') }} >unknown</button>
<p>{`count: ${count}`}</p>
</div>
);
}
思考:如果 state 是一个对象在 reducer 怎么处理?dispath 修改 state 时支持传参吗?接下来看看比较复杂的场景该如何实现。
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'addAge': // 修改年龄
return { ...state, age: state.age + 1 };
case 'updateName': // 修改名字
return { ...state, name: action.newName };
default:
return state;
}
}
const initalData = { name: '张三', age: 24 };
function Demo() {
const [person, dispatch] = useReducer(reducer, initalData);
return (
<div>
<button onClick={() => { dispatch({ type: 'addAge' }) }} >addAge</button> |
<button onClick={() => { dispatch({ type: 'updateName', newName: '李四' }) }} >updateName</button> |
<button onClick={() => { dispatch('unknown') }} >unknown</button>
<p>{`name: ${person.name}`}</p>
<p>{`age: ${person.age}`}</p>
</div>
);
}
用 useReducer 和 useContext 实现全局的状态管理
我们在学习 useContext 时实现了全局的状态共享逻辑,实现了在父组件中定义的状态在任何子孙组件中都可以获取并使用,但是并没有去实现修改共享状态的逻辑。而 useReducer 可以实现状态的修改逻辑,所以将 useContext 和 useReducer 结合起来是不是就可以实现对全局状态的管理,既可以获取全局状态,又可以修改全局状态,接下来就看看如何实现的
- 用 useContext 获取全局状态
- 用 useReducer 修改全局状态
-
用 React.createContext() 定义一个全局对象
import React from 'react'; const UserContext = React.createContext(); export default UserContext;
-
定义 reducer 函数
export default function reducer(state, action) { switch (action.type) { case 'login': // 登录 return { isLogin: true, ...action.user }; case 'logout': // 登出 return { isLogin: false }; case 'update': // 修改用户信息 return { ...state, ...action.user }; default: return state; } }
-
父组件中注入全局状态
import { useReducer, useContext } from 'react'; import UserContext from './context.js'; import reducer from './reducer.js'; function Parent() { // 用 useReducer 生成一个全局的用户信息对象和 dispatch 函数 const [state, dispath] = useReducer(reducer, { isLogin: false }); return ( <UserContext.Provider value={{ userInfo: state, dispatch }}> <Login /> <Logout /> <Update /> <User /> </UserContext> ); }
-
在登录,登出和修改用户信息的组件中分别派发不同的事件
function Login() { // 获取父组件中注入的全局状态 const state = useContext(UserContext); const login = () => { // 因为 useReducer 返回的 dispatch 以经在父组件中被注入了,所以这里可以直接拿来用 state.dispatch({ // dispatch 派发登录行为 type: 'login', user: { name: '张三', mobile: '123456' } }); } return ( <button onClick={login}>登录</button> ); } function Logout() { const state = useContext(UserContext); const logout = () => { state.dispatch({ type: 'logout' }); // dispatch 派发登出行为 } return ( <button onClick={logout}>登出</button> ); } function Update() { const state = useContext(UserContext); const update = () => { state.dispatch({ // dispatch 派发修改用户信息的行为 type: 'update', user: { age: 25 } }); } return ( <button onClick={update}>修改信息</button> ); }
-
在 User 组件中展示用户信息
function User() { // 使用 useContext 获取全局对象 const state = useContext(UserContext); const { userInfo } = state; return ( <div> { userInfo.isLogin ? <> <p>{`name: ${userInfo.name}`}</p> <p>{`age: ${userInfo.age}`}</p> </> : <> <p>未登录</> </> } </div> ); }
总结:中心思想还是刚才讲的两点,使用 useReducer 去修改全局状态,使用 useContext 去获取全局状态。
这样,我们不管在登录组件 、登出组件还是修改用户信息组件中通过 dispatch 派发事件去修改全局的 userInfo 时,都能在 user 组件中监听到状态的变化,从而展示不通的内容,这样就实现了组件之间的状态共享和修改逻辑,至此,一个简单的状态管理功能就实现了。
思考:既然 useState 和 useReducer 都能实现组件的状态管理,那么我们该怎么选择呢?
答案:组件自己内部的状态用 useState 就可以了,如果设计到组件之间的状态共享可以考虑使用 useReducer
如何自定义hook
React 规定所有的 hook 都要以 use 开头,hook 其实就是一个函数,也支持传参,支持返回值,也可以没有返回值,而且在我们自己定义的 hook 中也可以用已经实现的 hook,接下来我们就自己动手实现两个简单的 hook。
1. useTitle -- 给网页设置标题的 hook
假设有一个场景,我们需要在某些页面上设置特殊的 title,在其他页面上再将 title 恢复成原来的,该怎么实现呢?
首先,我们的 hook 肯定得有一个参数,这个参数就是我们要设置的 title,其次判断要不要有返回值,因为我们只需要将 document.title 给改成我们想要的就行,不用接收返回,所以这个 hook 是不需要返回值的,接下来就看看代码是怎么实现的。
import { useEffect, useRef } from 'react';
function useTitle(title) {
const defaultTitle = useRef(document.title);
useEffect(() => {
document.title = title;
return () => {
document.title = defaultTitle.current;
}
}, [title]);
// return 不需要返回值
}
2. useCookie -- 操作 cookie
一般在登录某个应用的时候都会将用户的 id 或者其他信息存储到 cookie 中去,注销登录的时候将 cookie 给清除,我们就可以自定义一个操作 cookie 的 hook,帮助我们保存和修改 cookie。
React 自带的 useState 也是实现对状态的更改获取功能的,useCookie 也是类似,我们也要实现更改存储和获取cookie,所以仿照着 useState,useCookie 应该也有一个参数,这个参数我们要操作的 cookie 的 key,因为 cookie 有过期时间,以及生效的 domain 等配置,所以还应该有一个配置的参数。返回的也是一个数组,数组的第一项是这个 cookie 的值,第二项是操作 cookie 的方法。
const [userId, setUserId] = useCookie('userId', options);
我们要修改 userId 这个 cookie 的值时,只需要用 setUserId 即可,接下来就来一步一步实现吧。
import { setState } from 'react';
import Cookies from 'js-cookie';
function useCookie(key, options) {
const [state, setState] = useState(() => {
const cookieValue = Cookies.get(cookieKey);
if (isString(cookieValue)) return cookieValue;
return options.defaultValue;
});
const updateCookie = (newValue) => {
setState((prevState) => {
// 不传 value 就默认移除 cookie
if (newValue === undefined) {
Cookies.remove(cookieKey);
} else {
Cookies.set(cookieKey, newValue, options);
}
return newValue;
});
}
}
推荐的 hook 库
总结
以上就是react中常用的一些hooks,学会了这几个基本上日常开发react项目就基本没有啥问题。在回顾的过程中也悟出了一个道理,编程编程最重要的点在于“编”,只有多“编”,才能让自己写代码的水平提高,几个月不写,之前闭着眼都会用的技术也能忘个七七八八,新掌握的技术几天不看,再次翻开还是跟新的一样,完全不会。所以,代码不能停,coding is never stop!