学习react Hooks的笔记
上一篇文章(全面掌握useReducer)我们用useReducer和useContext实现了一个切换主题的功能,useReducer我们已经掌握了,那么useContext又有哪些知识需要学习呢?
我们知道,每一个hook的诞生都有它独特的使命,比如useState是解决组件内状态更新的问题,useReducer是解决组件复杂状态更新的问题。
而useContext要解决的是:让开发者在某些场景下,从多层嵌套传值的组件树中解脱出来;useContext实现的是:让开发者通过context实现跨层级共享状态。
现在我们已经了解了useContext的主要作用,让我们开始今天的学习吧。
基础用法
- 创建 Context
首先,我们需要使用React.createContext创建一个context对象:
1
| const MyContext = React.createContext(defaultValue);
|
这里的defaultValue是当组件不在任何 Context Provider 内部时的默认值,defaultValue可以用 null,但 React 官方建议提供一个有意义的默认值,这样可以让调用usecontext组件更安全。
- 使用 Context Provider
为了在组件树中使用这个context,我们需要使用<MyContext.Provider>组件,它接受一个valueprop,这就是你想在它的子组件中共享的值。
1 2 3
| <MyContext.Provider value={someValue}> {/* 子组件 */} </MyContext.Provider>
|
- 在组件中访问 Context
在函数组件中,可以使用useContexthook 来访问这个 context 的值。
1 2 3 4
| function MyComponent() { const contextValue = useContext(MyContext); return <div>{contextValue}</div>; }
|
这里的contextValue就是第二步传入的someValue,而且contextValue获取到的永远是最新的值。
一个示例
我们来看一个更直观的示例:
使用reducer函数的注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); }
function Toolbar() { return ( <div> <ThemeButton /> </div> ); }
function ThemeButton() { const theme = useContext(ThemeContext); return <button>{theme} theme</button>; }
export default App;
|
这个示例中,App 中引用了ThemeContext并传了值,ThemeButton 是 App 的孙组件,这二者之间没有通过 Toolbar 进行嵌套传值,但是 ThemeButton 依然通过useContext拿到了 App 里的值。
从这个示例中我们可以总结出,React.createContext和useContext共同组成了一个管道,通过这个管道,开发者可以进行跨组件共享状态。
进阶技巧
动态Context值
有的时候 Context 传的值需要动态变化,我们可以基于useState去更新状态,更新后的值会实时反应到调用 Context 的组件上。
1 2 3 4 5 6 7 8 9 10 11 12
| function ThemeProvider({ children }) { const [theme, setTheme] = useState("light");
return ( <ThemeContext.Provider value={theme}> <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}> Toggle Theme </button> {children} </ThemeContext.Provider> ); }
|
如果要更新对象也是可以的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import React, { useContext } from 'react';
const CurrentUserContext = React.createContext('light');
function App() { const [currentUser, setCurrentUser] = useState(null return ( <CurrentUserContext.Provider value={{ currentUser, setCurrentUser }} > <Toolbar /> </CurrentUserContext.Provider> ); }
function Toolbar() { return ( <div> <LoginButton /> </div> ); }
function LoginButton() { const { currentUser, setCurrentUser } = useContext(CurrentUserContext);
if (currentUser !== null) { return <p>You logged in as {currentUser.name}.</p>; }
return ( <Button onClick={() => { setCurrentUser({ name: 'Advika' }) }}>Log in as Weijunext</Button> ); }
export default App;
|
和useReducer共用
在大型应用中,通常会将useContext与useReducer结合起来使用,以便从组件中提取与某些状态相关的逻辑。上一篇文章即是用这种思路实现了主题切换,源码可查看Github:useReducer-useContext实现主题切换,本文不再重复。
覆盖Provider value
当我们调用多个相同 Context,会实现value的覆盖
1 2 3 4 5 6 7
| <ThemeContext.Provider value="dark"> ... <ThemeContext.Provider value="light"> <Footer /> </ThemeContext.Provider> ... </ThemeContext.Provider>
|
高级应用——性能优化
当我们在使用useContext时,一个经常被提到的问题是性能优化。如果不正确地使用,Context 可能导致不必要的组件渲染,从而影响应用性能。
为什么会出现性能问题?
当Provider的value属性值发生变化时,所有使用了useContext的组件都将重新渲染。如果value经常变化,或者消费者组件很多,那么这会引起大量的不必要的渲染。
- 怎样解决?
如果你的 context 包含许多不同的状态值,尝试将它们分解成更小的 context。例如,而不是只有一个大的 AppContext,你可以有 UserContext、ThemeContext 等。这样,当某一部分的数据发生变化时,只有依赖于那部分数据的组件会重新渲染。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null); const CurrentUserContext = createContext(null);
export default function MyApp() { const [theme, setTheme] = useState('light'); const [currentUser, setCurrentUser] = useState(null); return ( <ThemeContext.Provider value={theme}> <CurrentUserContext.Provider value={{ currentUser, setCurrentUser }} > <Toolbar /> <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}> Toggle Theme </button> </CurrentUserContext.Provider> </ThemeContext.Provider> ) }
|
这与上一点相似。你可以为应用中的不同部分使用不同的 context 提供者,确保仅当真正需要的数据更改时才重新渲染组件。
- 使用useMemo和useCallback优化value
为了避免value变化造成子孙组件频繁的重新渲染,可以使用useMemo和useCallback对参数和方法进行缓存,减少value的无意义更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { useCallback, useMemo } from 'react';
function MyApp() { const [currentUser, setCurrentUser] = useState(null);
const login = useCallback((response) => { storeCredentials(response.credentials); setCurrentUser(response.user); }, []);
const contextValue = useMemo(() => ({ currentUser, login }), [currentUser, login]);
return ( <AuthContext.Provider value={contextValue}> <Page /> </AuthContext.Provider> ); }
|
注:如果你的应用状态经常发生变化,并触发大量组件的更新,那么这种情况不适合用useContext,请立即考虑其它状态更新方案。
结语
希望本文所讲的useContext应用和技巧能帮助你掌握useContexthook。
参考文档
掘金
精读React hooks