javascript - React Context API 和避免重新渲染

标签 javascript reactjs react-redux

我已经在底部更新了这个

有没有一种方法可以通过多个 Context API 消费者处理他们自己的提供者值部分来维护一个单一的根状态(如 Redux),而不会在每个孤立的更改上触发重新渲染?

已经read through this related question并尝试了一些变体来测试那里提供的一些见解,但我仍然对如何避免重新渲染感到困惑。

完整代码如下,在线:https://codesandbox.io/s/504qzw02nl

问题是,根据 devtools,每个组件都会看到“更新”(重新渲染),即使 SectionB 是唯一看到任何渲染更改的组件,即使 b 是状态树中唯一发生变化的部分。我已经尝试使用功能组件和 PureComponent 进行此操作,并看到相同的渲染抖动。

因为没有任何东西作为 Prop 传递(在组件级别),所以我看不出如何检测或防止这种情况。在这种情况下,我将整个应用程序状态传递给提供者,但我也尝试传递状态树的片段并看到相同的问题。显然,我做错了什么。

import React, { Component, createContext } from 'react';

const defaultState = {
    a: { x: 1, y: 2, z: 3 },
    b: { x: 4, y: 5, z: 6 },
    incrementBX: () => { }
};

let Context = createContext(defaultState);

class App extends Component {
    constructor(...args) {
        super(...args);

        this.state = {
            ...defaultState,
            incrementBX: this.incrementBX.bind(this)
        }
    }

    incrementBX() {
        let { b } = this.state;
        let newB = { ...b, x: b.x + 1 };
        this.setState({ b: newB });
    }

    render() {
        return (
            <Context.Provider value={this.state}>
                <SectionA />
                <SectionB />
                <SectionC />
            </Context.Provider>
        );
    }
}

export default App;

class SectionA extends Component {
    render() {
        return (<Context.Consumer>{
            ({ a }) => <div>{a.x}</div>
        }</Context.Consumer>);
    }
}

class SectionB extends Component {
    render() {
        return (<Context.Consumer>{
            ({ b }) => <div>{b.x}</div>
        }</Context.Consumer>);
    }
}

class SectionC extends Component {
    render() {
        return (<Context.Consumer>{
            ({ incrementBX }) => <button onClick={incrementBX}>Increment a x</button>
        }</Context.Consumer>);
    }
}

编辑:我知道可能有 a bug in the way react-devtools检测或显示重新渲染。我有 expanded on my code above以显示问题的方式。我现在无法判断我正在做的事情是否真的导致了重新渲染。根据我从 Dan Abramov 那里读到的内容,我认为我正确地使用了 Provider 和 Consumer,但我无法确定这是否属实。我欢迎任何见解。

最佳答案

有一些方法可以避免重新渲染,也可以使您的状态管理“类似于 redux”。我将向您展示我是如何做的,它远非 redux,因为 redux 提供了很多实现起来并不简单的功能,比如从任何 Action 或 combineReducers 将 Action 分派(dispatch)给任何 reducer 的能力等等许多其他人。

创建你的reducer

export const initialState = {
  ...
};

export const reducer = (state, action) => {
  ...
};

创建您的 ContextProvider 组件

export const AppContext = React.createContext({someDefaultValue})

export function ContextProvider(props) {

  const [state, dispatch] = useReducer(reducer, initialState)

  const context = {
    someValue: state.someValue,
    someOtherValue: state.someOtherValue,
    setSomeValue: input => dispatch('something'),
  }

  return (
    <AppContext.Provider value={context}>
      {props.children}
    </AppContext.Provider>
  );
}

在应用的顶层或您需要的地方使用您的 ContextProvider

function App(props) {
  ...
  return(
    <AppContext>
      ...
    </AppContext>
  )
}

将组件写成纯函数式组件

这样他们只会在那些特定的依赖更新为新值时才重新渲染

const MyComponent = React.memo(({
    somePropFromContext,
    setSomePropFromContext,
    otherPropFromContext, 
    someRegularPropNotFromContext,  
}) => {
    ... // regular component logic
    return(
        ... // regular component return
    )
});

具有从上下文中选择 Prop 的功能(如 redux map...)

function select(){
  const { someValue, otherValue, setSomeValue } = useContext(AppContext);
  return {
    somePropFromContext: someValue,
    setSomePropFromContext: setSomeValue,
    otherPropFromContext: otherValue,
  }
}

编写一个 connectToContext HOC

function connectToContext(WrappedComponent, select){
  return function(props){
    const selectors = select();
    return <WrappedComponent {...selectors} {...props}/>
  }
}

把它们放在一起

import connectToContext from ...
import AppContext from ...

const MyComponent = React.memo(...
  ...
)

function select(){
  ...
}

export default connectToContext(MyComponent, select)

用法

<MyComponent someRegularPropNotFromContext={something} />

//inside MyComponent:
...
  <button onClick={input => setSomeValueFromContext(input)}>...
...

我在其他 StackOverflow 问题上做的演示

Demo on codesandbox

避免重新渲染

MyComponent 仅当来自上下文的特定 Prop 更新为新值时才会重新呈现,否则它将保留在那里。 select 中的代码将在每次上下文更新的任何值时运行,但它什么都不做并且很便宜。

其他解决方案

我建议检查一下 Preventing rerenders with React.memo and useContext hook.

关于javascript - React Context API 和避免重新渲染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51317371/

相关文章:

javascript - Chrome 对已加载的图像发出 GET 请求?

reactjs - enzyme shallowWrapper.setState 不适用于redux 连接组件

javascript - 遍历对象数组显示语法错误reactjs

javascript - 使用 React-Redux 时更新输入字段的状态

reactjs - 我想从 REDUX REDUCER 中的 REDUX STORE 数组对象访问特定索引对象

javascript - 特定类型的数组,以及可能的好处和浏览器支持

javascript - 在 JavaScript 中封装尾部调用优化的实用程序?

javascript - getElementById() 没有返回正确的字符串

javascript - 对过滤器索引使用react更新状态

javascript - 过滤Json数组多键