首页IT科技react context优化四重奏教程示例

react context优化四重奏教程示例

时间2025-06-20 17:36:46分类IT科技浏览3880
导读:一、前言 我们在使用react的过程中,经常会遇到需要跨层级传递数据的情况。props传递数据应用在这种场景下会极度繁琐,且不利于维护,于是context应运而生...

一            、前言

我们在使用react的过程中            ,经常会遇到需要跨层级传递数据的情况              。props传递数据应用在这种场景下会极度繁琐                    ,且不利于维护      ,于是context应运而生

官方解释: Context 提供了一种在组件之间共享此类值的方式         ,而不必显式地通过组件树的逐层传递 props

二                    、用法

在正文之前                    ,先简单介绍一下context的三种消费方法:

1.通过Consumer来消费上下文
const globalContext = React.createContext(); class TestUseContextSon1 extends React.Component { render() { return ( <globalContext.Consumer> {(value) => { return <div>{value.num}</div>; }} </globalContext.Consumer> ); } } export default class TestUseContext extends React.Component { constructor(props) { super(props); this.state = { num: 2, }; } render() { return ( <globalContext.Provider value={{ num: this.state.num }}> <TestUseContextSon1 /> </globalContext.Provider> ); } }
2.通过静态变量contextType来消费上下文
const globalContext = React.createContext(); class TestUseContextSon2 extends React.Component { static contextType = globalContext; render() { return <div>{this.context.num}</div>; } } export default class TestUseContext extends React.Component { ...省略... render() { return ( <globalContext.Provider value={{ num: this.state.num }}> <TestUseContextSon2 /> </globalContext.Provider> ); } }
3.通过hooks useContext来消费上下文
const globalContext = React.createContext(); const TestUseContextSon3 = (props) => { const con = useContext(globalContext); return <div>{con.num}</div>; }; export default class TestUseContext extends React.Component { ...省略... render() { return ( <globalContext.Provider value={{ num: this.state.num }}> <TestUseContextSon3 /> </globalContext.Provider> ); } }

比较:

Consumer既可以在类组件中使用         ,也可以在函数组件中使用 contextType只能在类组件中使用 useContext只能在函数组件中使用

三      、缺点

这里有一个例子:

import React, { useState } from "react"; const globalContext = React.createContext(); const Son1 = () => { return <div>Son1</div>; }; const Son2 = () => { const value = useContext(globalContext); return <div>Son2---{value.num}</div>; }; export const Demo = () => { const [value, setValue] = useState({ num: 1 }); return ( <globalContext.Provider value={value}> <Son1 /> <Son2 /> </globalContext.Provider> ); };

当我们改变value值时      ,会导致Son1         、Son2都发生重渲染                    ,但这与我们的初衷相悖             ,造成了额外的开销   ,我们期望做到的是Son1不执行                   ,Son2重新渲染                  。在较长的一段时间内                 ,我都认为是使用了context导致Provider下面的子组件发生了重渲染      。网上也有很多解释没有说清楚,容易误导人           。

实际情况是value的变化导致了Son1                    、Son2发生重渲染                  。如下示例: 即使我们不使用·context               ,当value发生变化时                    ,Son1         、Son2也会重渲染         。

const Son1 = () => { return <div>Son1</div>; }; const Son2 = () => { return <div>Son2</div>; }; export const Demo = () => { const [value, setValue] = useState({ num: 1 }); return ( <Son1 /> <Son2 /> ); };

那么问题来了   ,我们使用context的时候            ,必然要向<globalContext.Provider value={value}>Provider的value中传入一个状态                    ,但是当状态改变时又不可避免的造成Provider下的所有子组件重新渲染      ,我们期望只有消费了上下文的子组件重新渲染         ,那么有什么方法能够避免这种额外的开销吗?

四      、context优化

我们知道                    ,所有消费了context的地方         ,只要Provider的value值发生变化      ,都会发生重渲染.只要我们有什么办法能够避开父组件状态发生变化                    ,引起的子组件状态发生变化             ,那就可以减少很多不必要的开销        。

一重奏--使用PureComponent

const globalContext = React.createContext(); class TestUseContextSon2 extends React.PureComponent { constructor(props) { super(props); this.state = {}; } render() { console.log("TestUseContextSon2----render"); return ( <globalContext.Consumer> {(value) => { console.log("Consumer----handle"); return <div>{value.num}</div>; }} </globalContext.Consumer> ); } } const TestUseContext = () => { const [value, setValue] = useState({ num: 1 }); return ( <globalContext.Provider value={value}> <button onClick={() => setValue({ num: value.num + 1 })}> 点击 </button> <TestUseContextSon2 /> </globalContext.Provider> ); }

初始化的时候   ,两个console各执行一遍

点击按钮之后                   ,TestUseContextSon2----render没有打印                 ,Consumer----handle打印,达到预期结果                   。

二重奏--使用shouldComponentUpdate

此处由于作者比较任性               ,省略100字                    ,基本效果其实和PureComponent一致   ,不做过多描述            。

三重奏--使用React.memo

React.memo既可以用于函数组件            ,也可以用于类组件

const globalContext = React.createContext(); const TestUseContextSon3 = React.memo(function (props) { console.log("TestUseContextSon3----render"); return ( <globalContext.Consumer> {(value) => { console.log("Consumer----handle"); return <div>{value.num}</div>; }} </globalContext.Consumer> ); }); const TestUseContext = () => { const [value, setValue] = useState({ num: 1 }); return ( <globalContext.Provider value={value}> <button onClick={() => setValue({ num: value.num + 1 })}> 点击 </button> <TestUseContextSon3 /> </globalContext.Provider> ); }

点击按钮之后                    ,TestUseContextSon2----render没有打印      ,Consumer----handle打印         ,达到预期结果    。 那如果我们使用useContext来消费上下文呢?

const TestUseContextSon4 = React.memo(function (props) { const con = useContext(globalContext); console.log("TestUseContextSon4----render"); return &lt;div&gt;{con.num}&lt;/div&gt;; });

点击按钮之后                    ,TestUseContextSon4----render打印,也就是说当我们使用useContext来消费上下文的时候         ,整个函数组件会重新执行                    。而Consumer仅仅只是局部执行      ,这意味更少的性能消耗               。

四重奏--Provider再封装+props.children

上面所述的三种方法都存在一个弊端                    ,Provider的直接下级组件都需要用memo                    、PureComponent             、shouldComponentUpdate处理             ,才能屏蔽掉父级状态变化带来的影响   ,那么有没有一种更方便的方式呢?

代码如下:

/** 主题 */ const ThemeContext = React.createContext({ theme: "red" }); const ThemeProvider = (props) => { const [theme, setTheme] = useState({ theme: "red" }); console.log("ThemeProvider-----", theme.theme); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {props.children} </ThemeContext.Provider> ); }; const Son1 = function (props) { const { setTheme } = useContext(ThemeContext); return <button onClick={() => setTheme({ theme: "blue" })}>改变主题</button>; }; const Son2 = function (props) { const { theme } = useContext(ThemeContext); console.log("Son2----", theme.theme); return <div>主题----{theme.theme}</div>; }; const Son4 = function (props) { console.log("Son4---没有使用上下文"); return <div>没有使用上下文</div>; }; export default class ContextChildren extends React.Component { render() { return ( <ThemeProvider> <Son1 /> <Son2 /> <Son4 /> </ThemeProvider> ); } }

在上面这段代码中                   ,<Son1 />   、<Son2 />                   、<Son3 />并没有直接放到ThemeContext.Provider组件下面                 ,而是将该组件再次封装成ThemeProvider组件,并将状态管理也放在ThemeProvider组件中,然后通过props.children来引入组件子节点。

效果如下:

当我们点击按钮时               ,打印如下:

点击按钮,setTheme执行                    ,状态由{ theme: "red" }变为{ theme: "blue" },引起ThemeProvider组件重新执行   ,打印ThemeProvider----- blue,组件Son2由于消费了上下文            ,重新执行                    ,打印Son2---- blue

那么问题来了      ,为什么没有打印Son4呢?我们没有使用memo                 、PureComponent等处理Son4组件,但是它确实不会重新执行                 。

出现这种现象         ,其实是props.children引起的                    ,props.children指向一个对象,这个对象中存放着<Son1 />、<Son2 />               、<Son3 />执行的结果         ,ThemeProvider执行的时候      ,props.children指向的对象没有发生变化                    ,只有当ContextChildren组件重新渲染的时候             ,<Son1 />                    、<Son2 />   、<Son3 />才会重新执行   ,由于我们将状态放置于ThemeProvider组件中                   ,所以ContextChildren组件不会重新渲染                 ,<Son1 />            、<Son2 />                    、<Son3 />也就不会重新执行,所以Son4---没有使用上下文没有打印                  。

那如果将ThemeProvider组件改成这样呢?

const ThemeProvider = (props) => { const [theme, setTheme] = useState({ theme: "red" }); console.log("ThemeProvider-----", theme.theme); const content = React.Children.map(props.children, (child) => { return child; }); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {content} </ThemeContext.Provider> ); };

Son4依然没有执行

再改一下:

const ThemeProvider = (props) => { const [theme, setTheme] = useState({ theme: "red" }); console.log("ThemeProvider-----", theme.theme); const content = React.Children.map(props.children, (child) => { return React.cloneElement(child); }); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {content} </ThemeContext.Provider> ); };

我们使用React.cloneElementapi克隆一下child

Son4执行了               ,我想这是因为克隆之后指向发生变化                    ,导致组件重新执行

总结

本文简单介绍了一下context的几种用法   ,以及如何来屏蔽父级状态变化(provider的value一般是和父级组件状态挂钩的)导致未消费上下文的子组件重新渲染导致的额外开销   。

以上就是react context优化四重奏教程示例的详细内容            ,更多关于react context 优化教程的资料请关注本站其它相关文章!

声明:本站所有文章                    ,如无特殊说明或标注      ,均为本站原创发布              。任何个人或组织         ,在未征得本站同意时                    ,禁止复制      、盗用         、采集                    、发布本站内容到任何网站         、书籍等各类媒体平台                  。如若本站内容侵犯了原著者的合法权益         ,可联系我们进行处理      。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
为什么深度学习用python