The React team released React 16.8 in February of this year, and if you’re reading this, you’ll know that it includes a new and anticipated feature: Hooks.
React-16.8 bug Hooks are also concerned about changes, including state management, so that you can easily search for them in communities:
Does React need Redux and Mobx now that it has Hooks?
Will React Hooks replace Redux?
Does Redux have Hooks support?
… .
While you may not have a clear answer to any of these questions after reading this article, the answer to one question is clear: state management is based on Hooks APIS.
Before we start reading, let’s look at a demo:
TODOMVC CodeSandbox (desktop or mobile browser is recommended to open, load takes dozens of seconds, need some patience ⌛️)
This is an overused Demo-Todolist, but it’s different in that it fixes state management with new Hooks syntax and a new library (and TypeScript)
Now, let’s start with this demo and make state management arrangements very clear
How was TodoList made
First: Declare global state
There are two words you can’t avoid when using Redux: Provider, connect, render props, or HOC
You’ll see a lot of developers writing libraries for Boilerplate Less, the most famous of which is Rematch, in order to write fewer lines of code. But even if you’re using the Context Api, you still have to write providers.
Take a look at the useState API for Hooks:
const Counter = (a)= > {
const [state, setState] = useState(0)
/ / a heap of UI
}
Copy the code
Think about it before
@connect( ... )
class Counter extends PureComponent {
render() {
const{... } =this.props // State loading...
/ / a heap of UI}}Copy the code
If only one day you could, you might think
const Counter = (a)= > {
const [state, actions] = useStore('Counter')
return <button onClick={()= > actions.increment(1)}>Increment</button>
}
Copy the code
Stranger:
Passerby: “By the way, let me see how your demo initializes components/App at the entrance.”
import * as React from "react";
import { useStore } from ".. /models/index";
import Header from "./Header";
// ...
import Filter from "./Filter";
const App = (a)= > {
const [state, actions] = useStore("Todo");
const incompletedCount = state.todos.reduce(
(count, todo) = >count + ! todo.completed,0
);
return (
<div>.</div>
);
};
export default App;
Copy the code
Passers-by withdrew a message.
Passerby: “But I think you must be… / Models /index: What’s wrong with it?
../models/index
import { Model } from "react-model"; import Todo from "./todo"; export const { useStore } = Model({ Todo }); // There is support for multiple modelsCopy the code
Passerby: “Emmmmmm, that./todo.ts open”
interface Todo {
// *$#x
}
type Filter = "All" | "Active" | "Completed";
// type *$#x
// type *$#x
// type *$#x
const defaultTodos = [
// ...
];
const model: ModelType<State, ActionParams> = {
state: {
todos: defaultTodos,
filter: "All",
id: 9
},
actions: {
add: (_, __, title) = > {
// ...
},
edit: (_, __, info) = > {
// ...
},
done: (_, __, id) = > {
return state= > {
// ...
};
},
allDone: (a)= > {
// ...
},
undo: (_, __, id) = > {
// ...
},
// ...}};export default model;
Copy the code
After five seconds…
Passer-by: “What’s the difference? The type, interface, action parameters are all underlined. The return method in actions has object and function.
Type and interface are TypeScript developers’ type definitions, which ultimately produce ModelType
to represent the type of the model. Thus, the state and actions returned by the useStore call are typed
And the underscore represents that parameter. If you don’t use the underscore, lint will report an error. If you use all of the parameters in a method, it should look like this
actions: {
allNeed: (state, actions, title) = > { // We can add async in front of it
// state is the state of the current model,
// Actions are the other actions in the current model
// You can do this
actions.add('titile')
// Merge the current state automatically, i.e. return {... state, ... object}
// If function is returned, the immer library is used to operate on the original state to generate the next IMmutable state
// This is useful for deep nesting of attributes
/ / return the object
return {
/ /... State, the library already does this for you, no need to write :)key: { ... state.key, deepkey: { ... state.key.deepkey// Passerby: Ok, that's enough. I understand
titles: [...state.key.deepkey, title]
}
}
}
/ / return function
return state= > {
state.key.deepkey.push(title) // Passerby: Yes, I have something}}},Copy the code
Passerby: “It looks good. Go on.”
Once the data is initialized, each function component can use useStore to subscribe to the required Model (s). By default, function components that subscribe to the same Model will fire rerender when the data is updated
components/Filter.tsx
const Filter = (a)= > {
const [state, actions] = useStore("Todo");
return (
<ul className="filters">.</ul>
);
};
Copy the code
components/TodoList.tsx
const TodoList = (a)= > {
const [state] = useStore("Todo");
return(...). }Copy the code
So when you click on All, Active, Completed, the TodoList display changes accordingly.
However, when the number of toDos is very, very large, such as 1W, the default subscription method has a very high performance overhead. Each data update takes 2s. Therefore, useStore provides the second parameter depActions, which is an array. The component rerender will only be rerender when the actions in the array are executed, and this should also be used with the react. memo. I wrote a simple Demo that you can refer to if necessary 🙂
Man B: “I still have middleware on Redux. Does this library have it?”
You have to go through the source code of the library
Object.keys(Global.State[modelName].actions).map(
key= >
(updaters[key] = async (params: any, middlewareConfig? :any) = > {const context: Context = {
modelName,
setState,
actionName: key,
next: (a)= > {},
newState: null,
params,
middlewareConfig,
consumerActions,
action: Global.State[modelName].actions[key]
}
await applyMiddlewares(actionMiddlewares, context)
})
)
Copy the code
This shows that the actions returned by useStore are all determined by actionMiddlewares. The component subscription Store mentioned above, Rerender, redux DevTools support not mentioned, try catch for action in production, and so on are all implemented by Middleware.
If you want to optimize or manipulate a particular Store, you just need to do the actionMiddlewares.
import { actionMiddlewares, middlewares } from 'react-model'
// Replace the middleware that returns new states from actions with middleware with timeout logic in production mode
if (process.env.NODE_ENV === 'production') {
actionMiddlewares[1] = middlewares.getNewStateWithCache(3000)}Copy the code
If you want to write your own middleware, it is also very easy to see how the global tryCatch of actions is done:
const tryCatch: Middleware<{}> = async (context, restMiddlewares) => {
const { next } = context
await next(restMiddlewares).catch((e: any) = > console.log(e))
}
Copy the code
The receive parameter has all the context available for the current actions execution and the middleware to be executed later, passing the baton to the next middleware with context.next.
Passerby B: “Redux supports SSR. Hooks to do SSR.”
First of all, next.js is fully function compatible, and SSR demos and documentation are available on Github
(Passerby B sneaks away
Oh, today is not early, the basic usage here is about the same, and anyway is also the first, here to close the pen (there is (below) I do not know, what I have not thought about
Finally, if you like this article, please feel free to like and share comments. It would be better if you could give a Star 😘