The history of React managed state

Get into the habit of writing together! This is the second day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

I believe that everyone is quite familiar with the state management of React, but I don’t know if you have encountered a 状态进行回退或前进situation that needs to be correct. Let me briefly talk about the history of how to manage the state.

There is a relatively common requirement:
就是页面上有一项数据是需要打开弹框选择的,这时候就涉及一个数据同步问题,页面的是原始数据,弹框的是编辑数据。

image.png1. When the pop-up box is opened, the original data of the page will be synchronized to the pop-up box -->> When the user clicks OK, the new data needs to be synchronized to the original data;
2. When the user clicks cancel -->> The edited data needs to be Restore to original data. So we have to write a forward method and a backward method. The following is the pseudo code.

const [initialData, setInitialData] = useState('naruto');
const [currentData, setCurrentData] = useState(initialData);

const forward = () => {
	setInitialData(currentData);
}

const backward = () => {
	setCurrentData(initialData);
}

return (
	<>
		<button onClick={backward}>取消</button>
		<button onClick={forward}>确定</button>
	</>
)
复制代码

This is a relatively simple interaction. Only the data of two time points are synchronized with each other. What if it is a little more complicated? image.pngA complex editable table with rollback and forward functions. At this time, we need to record every change node of the state. It is best to abstract this function into a hook, which is as convenient as using useState.

It just so happened that the editor has recently relied heavily on ahoos (ahoos has just released version 3.0, adding a lot of practical hooks), which provides a hook called useHistoryTravel, and I looked at the API and found that it completely meets the needs.

import { useHistoryTravel } from 'ahooks';
import React from 'react';
 
export default () => {
  const { value, setValue, backLength, forwardLength, back, forward } = useHistoryTravel<string>();
 
  return (
    <div>
      <input value={value || ''} onChange={(e) => setValue(e.target.value)} />
      <button disabled={backLength <= 0} onClick={back} style={{ margin: '0 8px' }}>
        back
      </button>
      <button disabled={forwardLength <= 0} onClick={forward}>
        forward
      </button>
    </div>
  );
};
复制代码

At the time of initialization, the back method and the forward method are provided to roll back or forward the state, which is very convenient. So how is this hook implemented? Let's take a look at its source code:

const [history, setHistory] = useState<IData<T | undefined>>({
  present: initialValue,
  past: [],
  future: [],
});
复制代码

It internally defines a state called history, which contains a current state, an array of past states and an array of future states. It can be seen that every change of the state is recorded, and then the state is freely traversed through the backward and forward methods.

const _forward = (step: number = 1) => {
   if (future.length === 0) {
     return;
   }
   const { _before, _current, _after } = split(step, future);
   setHistory({
     past: [...past, present, ..._before],
     present: _current,
     future: _after,
   });
 };
 
 const _backward = (step: number = -1) => {
   if (past.length === 0) {
     return;
   }
 
   const { _before, _current, _after } = split(step, past);
   setHistory({
     past: _before,
     present: _current,
     future: [..._after, present, ...future],
   });
 };
复制代码

After browsing the source code, I found that this seemingly complicated function can be realized so simply.

Although the function has been implemented, it still feels like something is missing. It would be more perfect if it could take a snapshot of the status like PS takes a snapshot of the history.

In fact, such a function is really needed. When you finish editing and need to save, it is actually a snapshot function. When you want to cancel the next editing, you can restore to the latest snapshot state.

Then let's take a look at the API of useHistoryTravel, and find that its reset method can pass in a new state, and then overwrite the new state with the original state defined during initialization, and then the next time the reset method is called,

The state will be restored to the original state that was last overwritten.

const reset = (...params: any[]) => {
  const _initial = params.length > 0 ? params[0] : initialValueRef.current;
  initialValueRef.current = _initial;
 
  setHistory({
    present: _initial,
    future: [],
    past: [],
  });
};
复制代码

A simple snapshot function can be realized, but as you can see from the code, every reset, past and future are emptied, is it not allowed to roll back after saving? This is obviously illogical, so I used a simple and effective method,

const { value: dataSource, setValue: setDataSource } = useHistoryTravel();
const { value: snapshot, setValue: setSnapshot} = useHistoryTravel();
复制代码

Just define a separate state to manage snapshots, haha.

Of course, if you want to achieve a more perfect implementation, of course, it is to expand directly on the basis of the source code. In my humble opinion, you can change the state definition to the following structure:

interface IValue<T> {
  value?: T;
  id: string;
  timestamp: number;
  snapshot: boolean;
}
 
interface IData<T> {
  current?: T;
  present: IValue<T>;
  past: IValue<T>[];
  future: IValue<T>[];
}
复制代码

In fact, it is to store the state value in an object, and add a unique id, timestamp, snapshot state, etc., whenever it is initialized or updated. These are very useful information. With this information, it is more flexible to use haha .

Guess you like

Origin juejin.im/post/7084074162741837861