文章目录
一、Hook 简介
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
什么是hook?
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数
使用规则
1、只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用
2、只能在 React 的函数组件 中调用 Hook。不要在其他 JavaScript 函数中调用
二、常见 hooks
1、useState
useState 允许你在函数组件中添加状态
1.1 基本用法
1、组件中导入 useState:
import React, { useState } from 'react';
2、在函数组件内部,调用 useState 并传入一个初始状态值。
useState 会返回一个新的数组
- 第一个值为当前状态值
- 第二个值为更新状态的函数
const [state, setState] = useState(initialState);
// state 是当前状态值
// setState 是一个函数,用于更新状态值
代码示例:
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
声明多个 state 变量
function ExampleWithManyStates() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
1.2 函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值
代码示例:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
1.3 惰性初始 state
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
2、useEffect
useEffect 可以让你在函数组件中执行副作用操作
组件挂载和更新时触发
核心代码:
不传第二个参数
useEffect(() => {
console.log("组件挂载和更新时触发,相当于 componentDidMount 和 componentDidUpdate");
});
代码示例:
import React, { useState, useEffect } from "react";
function UseEffect() {
const [count, setCount] = useState(0);
// 组件挂载和更新时触发,相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log("组件挂载和更新时触发,相当于 componentDidMount 和 componentDidUpdate");
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default UseEffect;
只在组件挂载时触发
核心代码:
第二个参数直接空数组 []
useEffect(() => {
console.log("只在组件挂载时触发,相当于 componentDidMount");
}, []);
代码示例:
import React, { useState, useEffect } from "react";
function UseEffect() {
const [count, setCount] = useState(0);
// 只在组件挂载时触发,相当于 componentDidMount
useEffect(() => {
console.log("只在组件挂载时触发,相当于 componentDidMount");
}, []);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default UseEffect;
组件更新时触发
核心代码:
count 值变化时触发
useEffect(() => {
console.log("组件更新时触发,相当于 componentDidUpdate");
}, [count]);
import React, { useState, useEffect } from "react";
function UseEffect() {
const [count, setCount] = useState(0);
// 组件更新时触发,相当于 componentDidUpdate
useEffect(() => {
console.log("组件更新时触发,相当于 componentDidUpdate");
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default UseEffect;
组件卸载时触发
说明:如果在 effect 中返回一个函数,则 React 将会在卸载时调用它
核心代码:
useEffect(() => {
console.log("只在组件挂载时触发");
return () => {
console.log("组件卸载时触发");
};
}, []);
代码示例:
import React, { useState, useEffect } from "react";
function UseEffect() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("只在组件挂载时触发");
// 返回一个函数,React 将会在执行清除操作时调用它
return () => {
console.log("组件卸载时触发");
};
}, []);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default UseEffect;
3、useContext
用于在函数组件中使用上下文
基本用法
3个步骤:
步骤一:使用 createContext 创建一个 context 对象(MyContext)
步骤二:顶层组件通过 MyContext.Provider,并在 value 上绑定值提供数据
步骤三:底层组件通过 useContext 获取参数
代码示例:
import { createContext, useContext } from "react";
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 1、使用 createContext 创建一个 context 对象 ThemeContext
const ThemeContext = createContext(themes.dark);
function ThemedButton() {
// 3、底层组件通过 useContext 获取参数
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
context 样式按钮
</button>
);
}
function Toolbar(props: any) {
return (
<div>
<ThemedButton />
</div>
);
}
function UseContext() {
return (
// 2、顶层组件通过 ThemeContext.Provider,并在 value 上绑定值提供数据
<ThemeContext.Provider value={themes.light}>
<Toolbar />
</ThemeContext.Provider>
);
}
export default UseContext;
4、useReducer
用于在函数组件中使用状态
useState 的替代方案,它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
语法:
const [state, dispatch] = useReducer(reducer, initialArg, init);
代码示例:
import { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<span>Count: {state.count}</span>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</>
);
};
export default UseReducer;
5、useCallback
用于在函数组件中缓存一个回调函数
把内联回调函数及依赖项数组作为参数传入
useCallback
,它将返回该回调函数的 memoized 版本该回调函数仅在某个依赖项改变时才会更新
代码示例:
import { useCallback, useState } from "react";
const UseCallback = () => {
const [count, setCount] = useState(0);
const [num, setNum] = useState(2);
// 当num变化时才会更新
const incrementCount = useCallback(() => {
setCount(count + num);
}, [num]);
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementCount}>加 count</button>
<p>当前count步进: {num}</p>
<button onClick={() => setNum(num + 1)}>加count步进</button>
</div>
);
};
export default UseCallback;
6、useMemo
用于在函数组件中缓存计算结果
语法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值
把“创建”函数和依赖项数组作为参数传入 useMemo
它仅会在某个依赖项改变时才重新计算 memoized 值。
这种优化有助于避免在每次渲染时都进行高开销的计算
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值
代码示例:
import React, { useMemo, useState } from "react";
const UseMemo = () => {
const [num1, setNum1] = useState(1);
const [num2, setNum2] = useState(2);
const add = useMemo(() => (a: number, b: number) => a + b, [num1, num2]);
return (
<div>
<div>
<span>num1: {num1}</span>
<button onClick={() => setNum1(num1 + 1)}>num1+</button>
<span>num2: {num2}</span>
<button onClick={() => setNum2(num2 + 2)}>num2+</button>
</div>
<div>求和:{add(num1, num2)}</div>
</div>
);
};
export default UseMemo;
7、useRef
用于在函数组件中创建一个 ref 对象,该对象可以用来访问组件的 DOM 节点或属性
语法:
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
返回的 ref 对象在组件的整个生命周期内持续存在
代码示例:
import { useRef } from "react";
const UseRef = () => {
const inputEl = useRef<HTMLInputElement>(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl?.current?.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
};
export default UseRef;
8、useImperativeHandle
用于在函数组件中提供响应式引用
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
useImperativeHandle 应当与 forwardRef 一起使用:
语法:
useImperativeHandle(ref, createHandle, [deps])
代码示例:
import { useEffect, useRef, useImperativeHandle, forwardRef } from "react";
// 子组件
const FancyInput = forwardRef((props: any, ref: any) => {
const inputRef: any = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.value = "Hello";
},
test: () => {
console.log("test");
},
}));
return <input ref={inputRef} />;
});
// 父组件
const UseImperativeHandle = () => {
let ref: any = useRef(null);
useEffect(() => {
console.log(ref);
// 调用子组件的 focus 方法
ref?.current?.focus();
// 调用子组件的 test 方法
ref?.current?.test();
});
return (
<>
<FancyInput ref={ref} />
</>
);
};
export default UseImperativeHandle;
9、useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
可以使用它来读取 DOM 布局并同步触发重渲染
在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
注:尽可能使用标准的 useEffect 以避免阻塞视觉更新
与 useEffect的区别:
useEffect 是异步触发
useLayoutEffect 是同步触发,适用于需要在DOM更新之前立即执行操作的情况
三、自定义 hook
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
作用:通过自定义 Hook,可以将组件逻辑提取到可重用的函数中
自定义一个hook
定义 hook useCount,实现自动 +1 的计数器
const useCount = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((count) => count + 1);
}, 2000);
// 当组件卸载时,清除定时器
return () => clearInterval(interval);
}, []);
return count;
};
使用 自定义hook
在组件中使用自定义 hook
// 组件1
const TextComp1 = () => {
const count = useCount();
return (
<div>
<h3>这是组件1:</h3>
{count}
</div>
);
};
// 组件2
const TextComp2 = () => {
const count = useCount();
return (
<div>
<h3>这是组件2:</h3>
<p style={{ color: count % 2 === 0 ? "green" : "red" }}>{count}</p>
</div>
);
};
// 父组件
const UseCustom = () => {
return (
<div>
<TextComp1 />
<TextComp2 />
</div>
);
};
完整示例的代码:
import { useEffect, useState } from "react";
// 自定义 hook useCount,实现自动 +1 的计数器
const useCount = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount((count) => count + 1);
}, 2000);
// 当组件卸载时,清除定时器
return () => clearInterval(interval);
}, []);
return count;
};
// 组件1
const TextComp1 = () => {
const count = useCount();
return (
<div>
<h3>这是组件1:</h3>
{count}
</div>
);
};
// 组件2
const TextComp2 = () => {
const count = useCount();
return (
<div>
<h3>这是组件2:</h3>
<p style={{ color: count % 2 === 0 ? "green" : "red" }}>{count}</p>
</div>
);
};
// 父组件
const UseCustomHook = () => {
return (
<div>
<TextComp1 />
<TextComp2 />
</div>
);
};
export default UseCustomHook;
推荐1个 github 上的封装的hooks
github地址:
https://github.com/alibaba/hooks
如需下载源码到本地:
git clone https://github.com/alibaba/hooks.git
中文文档:
https://ahooks.js.org/zh-CN/hooks/use-request/index