新的反应上下文 API 是否触发重新呈现?

我一直在尝试理解新的反应上下文 API,并正在使用它。我只是想检查一个简单的情况-什么所有重新呈现时,数据提供程序更新。

检查 < a href = “ https://Codesandbox.io/s/l2kx18n35z”rel = “ noReferrer”> Codesandbox 上的这个小例子

在我的例子中,我有一个 App组件,它的状态是这样的

this.state = {
number - A random number
text - A static text
}

我从这里创建一个新的 React Context,其中包含状态 numbertext,并将值传递给两个消费者 NumberText

因此,我的假设是,如果随机数更新,它将改变上下文和两个组件应触发重新呈现。

但在现实中,价值是更新,但没有重新渲染发生。

所以,我的问题是

  1. 是否更新为不通过常规重新呈现传播的上下文?因为当上下文发生变化时,我无法看到日志/颜色的变化。

  2. 是否更新了该提供程序的所有使用者?

111720 次浏览

Are updated to the context not propagated via the ususal rerenders? As I cannot see my logs / color changes when context changes.

The updates to context values doesn't trigger re-render for all the children of the provider, rather only components that are rendered from within the Consumer, so in your case although number component contains the Consumer, Number component isn't re-rendered, rather just the render function within the Consumer and hence the value changes on context updates. This way it is quite a lot performant as it doesn't trigger re-renders for all of its children.

Are all the consumers to that Provider updated or not ?

All consumers to that Provider will go through an update cycle but whether or not they re-render is decided by the react virtual DOM comparison. A demo of this you can see in the console for this sandbox

EDIT

What you need to make sure is that the components are rendered as children of the ContextProvider component and you are passing handlers to it instead of rendering them inline and updating the state of ContextProvider because that will trigger a re-render of all components that are within the ContextProvider

Performant usage

App.js

  constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
updateNumber: this.updateNumber,
};
}
render() {
return (
<AppContext.Provider
value={this.state}
>
{this.props.children}
</AppContext.Provider>
);
}

index.js

class Data extends React.Component {
render() {
return (
<div>
<h1>Welcome to React</h1>
<Number />
<Text />
<TestComp />
<AppContext.Consumer>
{({ updateNumber }) => (
<button onClick={updateNumber}>Change Number </button>
)}
</AppContext.Consumer>
</div>
);
}
}


const rootElement = document.getElementById("root");
ReactDOM.render(
<App>
<Data />
</App>,
rootElement
);

Less Performant usage

App.js

class App extends Component {
constructor() {
super();
this.state = {
number: Math.random() * 100,
text: "testing context api"
};
}


updateNumber = () => {
const randomNumber = Math.random() * 100;
this.setState({ number: randomNumber });
};


render() {
return (
<AppContext.Provider value={this.state}>
<div>
<h1>Welcome to React</h1>
<Number />
<Text />
<TestComp />
<button onClick={this.updateNumber}>Change Number </button>
</div>
</AppContext.Provider>
);
}
}

Here is an update for your questions based on the useContext Hook:

const value = useContext(MyContext)

When the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider. Even if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen starting at the component itself using useContext.

A component calling useContext will always re-render when the context value changes. If re-rendering the component is expensive, you can optimize it by using memoization.

So given below code example, components Number and Text will re-render with each context value change, as both directly contain useContext(AppContext).

const AppContext = React.createContext();


const Number = React.memo(props => {
const renderCount = useRenderCount();
const contextNo = React.useContext(AppContext);
return (
<div style=\{\{ backgroundColor: `${randomColor()}` }}>
Number: rendered {renderCount.current} times.
</div>
);
});


const Text = React.memo(() => {
const renderCount = useRenderCount();
const context = React.useContext(AppContext);
return (
<div style=\{\{ backgroundColor: `${randomColor()}` }}>
Text: rendered {renderCount.current} times. I rerender with context value
changes!
</div>
);
});


const App = () => {
const [ctxVal, setCtxVal] = React.useState(0);
const [prop, setProp] = React.useState(0);
return (
<AppContext.Provider value={ctxVal}>
<Number prop={prop} />
<Text />
<button onClick={() => setCtxVal(ctxVal + 1)}>
Change context value
</button>
<button onClick={() => setProp(prop + 1)}>
Only change prop in Number
</button>
</AppContext.Provider>
);
};


function useRenderCount() {
const renderCount = React.useRef(1);
React.useEffect(() => {
renderCount.current += 1;
});
return renderCount;
}


function randomColor() {
const letters = "0123456789ABCDEF"; let color = "#";
for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)];
return color;
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>