-
useState
-
基本语法
const [state, setState] = useState(initialState);
-
注意事项
- initialState只在初次渲染时保存为初始状态,后续渲染时将其忽略。如果传递函数的返回值作为initialState,那么这个函数应该不接受任何参数,并且要有一个返回值,此时,为了避免重复创建初始状态,传递的应该是函数本身,而不是函数调用的结果
const [todos, setTodos] = useState(createInitialTodos); //如果传递的是函数本身,则React仅在初始化期间调用它 const [todos, setTodos] = useState(createInitialTodos()); //若传递的是函数调用的结果,尽管 createInitialTodos() 的结果仅用于初始渲染, //但在每次重新渲染时都会调用此函数,这可能造成资源的浪费
- 若想传递一个函数作为state,则应该使用箭头函数的形式
const [fn, setFn] = useState(someFunction); function handleClick() { setFn(someOtherFunction); } //如果传递的直接是一个函数,则React 认为 someFunction 是一个初始化函数, //而someOtherFunction 是一个更新函数,于是会调用函数并存储函数调用的结果 const [fn, setFn] = useState(() => someFunction); function handleClick() { setFn(() => someOtherFunction); } //通过这种方式,React 将存储传递的函数作为state
- setState的两种方式:直接传递新状态,或者传递一个根据先前状态来计算新状态的函数。调用set函数不会改变已经执行的代码中当前的state,它只影响下一次渲染中useState返回的内容。传递更新函数,是获取的待定状态,并从中计算下一个状态。如果你提供的新值与当前state相同(由 Object.is 比较确定),React 将 跳过重新渲染该组件及其子组件
传递更新函数和直接传递下一个状态之间的区别:const [name, setName] = useState('Edward'); const [age, setAge] = useState(18); function handleClick() { setName('Taylor'); console.log(name) //依然是Edward setAge(a => a + 1); }
function handleClick() { setAge(age + 1); // setAge(42 + 1) setAge(age + 1); // setAge(42 + 1) setAge(age + 1); // setAge(42 + 1) } //这样的方式age将只会变为43而不是45 function handleClick() { setAge(a => a + 1); // setAge(42 => 43),42作为待定状态 setAge(a => a + 1); // setAge(43 => 44),43作为待定状态 setAge(a => a + 1); // setAge(44 => 45),44作为待定状态 } //这里的a => a + 1 是更新函数。它获取待定状态,并从中计算下一个状态,age最后会变成45
- 出现错误:“Too many re-renders”(可能是在渲染过程中调用了事件处理函数)
// 🚩 错误:在渲染过程中调用事件处理函数 return <button onClick={handleClick()}>Click me</button> // ✅ 正确:将事件处理函数传递下去 return <button onClick={handleClick}>Click me</button> // ✅ 正确:传递一个内联函数 return <button onClick={(e) => handleClick(e)}>Click me</button>
-
useEffect
-
基本语法
useEffect(() => { //setup const connection = createConnection(serverUrl, roomId); connection.connect(); //cleanup return () => { connection.disconnect(); }; }, [serverUrl, roomId]);
-
代码执行顺序
1. 将组件挂载到页面时,将运行 setup 代码。
2. 重新渲染 依赖项 变更的组件后
- 首先,使用旧的 props 和 state 运行 cleanup 代码。
- 然后,使用新的 props 和 state 运行 setup 代码。
3. 当组件从页面卸载后,cleanup 代码 将运行最后一次。
-
删除不必要的对象依赖项
const options = { // 🚩 这个对象在每次渲染时都是从头创建的 serverUrl: serverUrl, roomId: roomId }; useEffect(() => { const connection = createConnection(options); // 它在 Effect 内部使用 connection.connect(); return () => connection.disconnect(); }, [options]); // 🚩 因此,这些依赖在重新渲染时总是不同的 //应该改为: useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]);
-
删除不必要的函数依赖项
function createOptions() { // 🚩 此函数在每次重新渲染都从头开始创建 return { serverUrl: serverUrl, roomId: roomId }; } useEffect(() => { const options = createOptions(); // 它在 Effect 中被使用 const connection = createConnection(); connection.connect(); return () => connection.disconnect(); }, [createOptions]); // 🚩 因此,此依赖项在每次重新渲染都是不同的 //应改为: useEffect(() => { function createOptions() { return { serverUrl: serverUrl, roomId: roomId }; } const options = createOptions(); const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]);
-
useLayoutEffect
-
和useEffect的区别
useLayoutEffec是useEffect的一个版本,在浏览器重新绘制屏幕之前触发,会阻塞浏览器的渲染,而useEffect不会阻塞浏览器的渲染
-
useContext
-
基本语法
const value = useContext(SomeContext);
-
返回值
为传递给树中调用组件上方最近的 SomeContext.Provider
的 value
。如果没有这样的 provider,那么返回值将会是为创建该 context 传递给 createContext 的defaultValue。返回的值始终是最新的,如果 context 发生变化,React 会自动重新渲染读取 context 的组件。
-
用法一:向组件树深层传递数据
import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { const [theme, setTheme] = useState('dark'); return ( <ThemeContext.Provider value={theme}> <Form /> </ThemeContext.Provider> ) } function Form() { return ( <Panel title="Welcome"> <Button>Sign up</Button> <Button>Log in</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); }
-
用法二:在传递对象和函数时优化重新渲染
function MyApp() { const [currentUser, setCurrentUser] = useState(null); function login(response) { storeCredentials(response.credentials); setCurrentUser(response.user); } return ( <AuthContext.Provider value={{ currentUser, login }}> <Page /> </AuthContext.Provider> ); } //注:通过 context 可以传递任何值,包括对象和函数