不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
const [state, setState] = useState(initialState);
返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
useState 可以理解为一个普通 JavaScrip的函数,他的入参有两种类型:
const [count, setCount] = useState(0);
代表 count 的初始值是 0.
useState 的返回值是一个数组,格式为 [state, setState], 数组中的第一个元素是 state 的当前值,第二个元素是一个函数,用于设置 state 的值,setState 同样接受两种类型的参数:
onClick={()=>setCount(c=>c+1)}
代表,设置 count 的值,为 count 之前值 + 1
useEffect(didUpdate);
该 Hook 接收一个包含命令式、且可能有副作用代码的函数。
useEffect, 顾名思义,就是“使用副作用”,通常用到 Effect 的情况包括:设置定时器,发送网络请求等。先来看一下 useEffect 的构成。useEffect 接收两个参数,第一个是必传的 callback,第二个是选传的 dependencies, 类型为列表。例如 [a, b]。
可以理解为,每当 dependencies 中的一个或多个元素发生改变时,都会去执行一遍 callback.
两个特殊情况是:
useEffect(()=>{
const timer = window.setInterval(()=>{console.log(a)}, 1000);
return () => window.clearInterval(timer);
}, [a])
当 a 发生改变时,旧的 timer 会被 clear 掉,新的 timer 会重新生成。所以 useEffect callback 的 return 函数是清理副作用的最佳时机。
当 deps 为空列表时,useEffect 相当于 class 组件中的 componentDidMount 生命周期,return 函数的执行时机则相当于 componentWillUnmount.
deps不可传入ref和常量,字符串传入没意义。
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数。为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
const value = useContext(MyContext);
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
const ThemeContext = React.createContext(themes.light);
function App() {
return (
);
}
function Toolbar(props) {
return (
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
);
}
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。无论 useLayoutEffect 还是 useEffect 都无法在 Javascript 代码加载完成之前执行。
会比usseEffect早一些执行。
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
>
);
}
当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
通过使用 React.forwardRef API可以解决这个问题
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值。
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行不应该在渲染期间内执行的操作如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
const classNames = useMemo(() => {
return [NEW_DESCRIPTION_CLASSNAME, overflowedClassname].filter(Boolean).join(" ");
}, [overflowedClassname])
返回一个 memoized 回调函数
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
const backgroundColorChange = useCallback(() => {
if (!changeBC) {
setChangeBC(true);
setTimeout(() => {
setChangeBC(false)
}, 1000)
}
}, [changeBC])
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return ;
}
FancyInput = forwardRef(FancyInput);
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,useImperativeHandle 应当与 forwardRef 一起使用,减少暴露给父组件的属性,useImperativeHandle本身就是一次ref命令代码执行,As always, imperative code using refs should be avoided in most cases.
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return ;
}
FancyInput = forwardRef(FancyInput);
通过forwardRef,父组件获取子组件的ref,子组件在暴露ref中,限制暴露的一些参数
import { useRef,forwardRef,MutableRefObject,useImperativeHandle,Ref} from "react";
//只暴露value、getType、focus给父级
const InputEl = forwardRef((props: {}, ref: Ref): JSX.Element=>{
const inputEl: MutableRefObject = useRef();
useImperativeHandle(ref, ()=>({//第一个参数:暴露哪个ref;第二个参数:暴露什么
value: (inputEl.current as HTMLInputElement).value,
getType: () => (inputEl.current as HTMLInputElement).type,
focus: () => (inputEl.current as HTMLInputElement).focus()
}));
return(
)
})
//暴露整个input节点给父级
const InputEl = forwardRef((props: {}, ref: Ref): JSX.Element=>{
return(
)
});
//父级
function InputWithFocusButton() {
const inputEl: MutableRefObject = useRef(null);
function onButtonClick() {
console.log('子组件input的对象:', inputEl.current);
inputEl.current.focus();
};
return (
<>
>
);
}
Hook 介绍:useReducer 用于在函数组件中实现复杂的状态管理。它类似于 Redux 中的 reducer,接收一个纯函数和初始状态,并返回当前状态和一个派发函数,用于触发状态更新。它适用于需要处理多个相关状态变化并具有复杂逻辑的场景。
import React, { useReducer } from 'react';
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('Unexpected action');
}
}
function Example() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
const increment = () => {
dispatch({ type: 'increment' });
};
const decrement = () => {
dispatch({ type: 'decrement' });
};
return (
Count: {state.count}
);
}
export default Example;