A Secret parts of React New Context API
New Context API
React introduced a new Context API, which is to replace a legacy Context API.
The API is using a render props pattern so you can use the API flexible.
const LangContext = React.createContext(null);const App = () => (
<LangContext.Provider value="en">
<LangContext.Consumer>
{lang =>
<Message text="hello" lang={lang} />
}
</LangContext.Consumer>
</LangContext.Provider>
);
With new Context API, you can create a global store like Redux.
const store = {
state = {
foo: 1,
bar: 1,
},
update(cb) {
this.state = cb(this.state);
}
};const {Provider, Consumer} = React.createContext(store.state);const Foo = () => (
<Consumer>
{({foo, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, foo: state.foo + 1}))
}
>
click
</button>
<p>Foo is {foo}</p>
</div>
)}
</Consumer>
);const Bar = () => (
<Consumer>
{({bar, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, bar: state.bar + 1}))
}
>
click
</button>
<p>Bar is {bar}</p>
</div>
)}
</Consumer>
);const App = () => (
<Provider value={store.state}>
<Foo />
<Bar />
</Provider>
);
It’s cool, isn’t it? But the above example has a problem. If you click the Foo’s button, Foo and Bar are re-rendered regardless of which values are updated.
In order to solve the problem, the New Context API has a secret feature of observedBits
.
ObservedBits
The New Context API is able to optimize your updates for a context value, in order to do this, you have to specify changedBits
and observedBits
.
Let’s take a look.
changedBits
changedBits
are bits represents what context values are changed. You can specify a function returning changedBits
as the 2nd argument of React.createContext
. the function receives the previous value and the current value.
const store = {
observedBits: {
foo: 0b01,
bar: 0b10
},
state = {
foo: 1,
bar: 1,
},
update(cb) {
this.state = cb(this.state);
}
};const StoreContext = React.createContext(
store.state,
(prev, next) => {
let result = 0;
// foo has been changed
if (prev.foo !== next.foo) {
result |= store.observedBits.foo;
}
// bar has been changed
if (prev.bar !== next.bar) {
result |= store.observedBits.bar;
}
return result;
}
);
The above the implementation, if foo has been changed, the function returns 0b01
, if bar has been changed, it returns 0b10
. If both have been changed the function returns 0b11
.
unstable_observedBits
observedBits are bits represents what values Consumer are observed. You can specify the observedBits as the Consumer’s Props.
const Foo = () => (
<Consumer unstable_observedBits={store.observedBits.foo}>
{({foo, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, foo: state.foo + 1}))
}
>
click
</button>
<p>Foo is {foo}</p>
</div>
)}
</Consumer>
);const Bar = () => (
<Consumer unstable_observedBits={store.observedBits.bar}>
{({bar, update}) => (
<div>
<button
onClick={() =>
update((state) => ({...state, bar: state.bar + 1}))
}
>
click
</button>
<p>Bar is {bar}</p>
</div>
)}
</Consumer>
);
You can specify the observedBits as the unstable_observedBits
Props. This is a unstable feature so the Props has unstable_
prefix. When Consumer Component is passed unstable_observedBits
Props, Consumer is re-rendered only when unstable_observedBits & changedBits !== 0
.
If you are not familiar with bitwise operators, check the MDN references.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators
Please check the example.
BTW, React uses bitwise operators in its Fiber implementation, which is very interesting :)
React Fiber’s rendering mode is represented by bits.
export const NoContext = 0b00;
export const AsyncMode = 0b01;
export const StrictMode = 0b10;
React Fiber’s side effects also are represented by bits, which is possible to mask the effects by bitmasks (See the HostEffectMask
).
export const NoEffect = /* */ 0b000000000000;
export const PerformedWork = /* */ 0b000000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b000000000010;
export const Update = /* */ 0b000000000100;
export const PlacementAndUpdate = /* */ 0b000000000110;
export const Deletion = /* */ 0b000000001000;
export const ContentReset = /* */ 0b000000010000;
export const Callback = /* */ 0b000000100000;
export const DidCapture = /* */ 0b000001000000;
export const Ref = /* */ 0b000010000000;
export const ErrLog = /* */ 0b000100000000;
export const Snapshot = /* */ 0b100000000000;
// Union of all host effects
export const HostEffectMask = /* */ 0b100111111111;
export const Incomplete = /* */ 0b001000000000;
export const ShouldCapture = /* */ 0b010000000000;