preface

If you’ve used a Context, you can’t update a Context exactly like Redux. When one of the values in the Context changes, all the components that use the Context must be updated. If you use the Context for state management, there must be a small performance penalty.

However, when I read the Context source code, I found a method that was not described in the documentation. The createContext method and the useContext method both have a second parameter that can accurately update components that use a value in the Context without causing all components to be refreshed.

Realize the principle of

React style, using bits.

Bitwise or (| a | b) for each bit, when two operands corresponding bits of at least one 1, the result is 1, otherwise 0. Bitwise and (&) a & b For each bit, the result is 1 only if the corresponding bit bits of the two operands are 1, otherwise 0. | 0 0 0 b01 b01 results & 0 0 b01 result is 0Copy the code

We need to add a binary identifier to the field to indicate whether the value has changed.

const bits = {
  user: 0b01.password: 0b10,}Copy the code

When our component uses the Context, it needs to identify which fields the component uses.

const context = useContext(Context, bits.user); // This context uses the user field
Copy the code

When the value of the Context changes, a comparison function is run.

const calculateChangedBits = (oldValue, newValue) = > {
  let result = 0;
  // Indicates that the user field has changed
  if(oldValue.user ! == newValue.user){ result |= bits.user;// 0 -> 0b01
  }
  // Indicates that the password field has changed
  if(oldValue.password ! == newValue.password){ result |= bits.password;// 0 -> 0b10
  }
  return result;
}
Copy the code

If both the user and password fields change, the final result is 0b11.

React internally compares the value of this result to the second argument we passed in useContext, bits.user.

// observedBits is the second argument bits.user passed in when we use useContext
// changedBits is the value returned by calling the comparison function
if((observedBits & changedBits) ! = =0) {// Update this component
}
Copy the code

User is 0b01, 0b01&0b10 is 0, so the component will not be updated.

A case in point

You can check it out at Codesandbox.

import { createContext, useReducer, useContext, useRef } from "react";

// The calculation is that the values have changed
const calculateChangedBits = (oldValue, newValue) = > {
  let result = 0;
  Object.entries(newValue.state).forEach(([key, value]) = > {
    if (value !== oldValue.state[key]) {
      result |= bitsMap[key];
    }
  });
  return result;
};

// The field identifier
const bitsMap = {
  user: 0b01.password: 0b10
};

const initialValue = { user: "".password: "" };
const reducer = (state, { name, value }) = > ({ ...state, [name]: value });

// The second argument is the comparison function
const Context = createContext(initialValue, calculateChangedBits);

const Input = ({ name }) = > {
  // useContext The second parameter identifies which field the component uses
  const { state, setState } = useContext(Context, bitsMap[name]);
  const renderCount = useRef(0);
  ++renderCount.current;
  return (
    <div>
      <input
        type="text"
        value={state[name]}
        onChange={({ target}) = > setState({ name, value: target.value })}
      />
      <p>Render {rendercount.current} times</p>
    </div>
  );
};

const Provider = ({ children }) = > {
  const [state, setState] = useReducer(reducer, initialValue);
  return (
    <Context.Provider value={{ state.setState}} >{children}</Context.Provider>
  );
};

export default function App() {
  return (
    <Provider>
      <Input name="user" />
      <Input name="password" />
    </Provider>
  );
}
Copy the code

The second argument to useContext, in addition to the binary identifier, can also be passed false to indicate that no update is required, which can be useful when passing the Dispatch method.

const useDispatch = () = > {
  const { dispatch } = useContext(Context, false);
  return dispatch;
}
Copy the code

Used in ContextConsumer.

<Context.Consumer unstable_observedBits={bitsMap[name]}>
  {({ state }) = >{... }} </Context.Consumer>Copy the code

Something to watch out for

Because it is a binary value, so there is a maximum limit, such as on a 32-bit system’s largest binary values of 0 b111111111111111111111111111111, namely records only 31 field identification.

Since it is future and experimental, there is a warning on the console.

I didn’t find out how long the future was added, but it should be quite old. In addition to the warning, it is quite practical in some cases.

Sealing a package

Sealed a package to avoid handwriting binary.