Open source is not easy, thank you for your support, ❤ star me if you like concent ^_^
Here is a list of status management in the collection, welcome friends who are interested to know ^_^
awesome-state
In this article, we will see the big difference between composition style and hook style in react code.
Preface
composition api
(combination api) and optional api
(optional api) are two ways to organize code. I believe you have learned a lot in the various related introduction articles of vue3
, they can exist at the same time, It is not mandatory that you can only use which one, but the two major advantages of combined api do make developers more inclined to use it instead of optional api.
- Package the reusable logic based on the function and inject it into any component, making the decoupling of the view and business more elegant
- Let the businesses of the same function be placed more closely together without being separated, improving the development and maintenance experience
The above two points are elegantly solved by hook
in React, so what are the advantages of combined api compared to hook
? I’m not going to sell it here. I believe that some friends have already known when Youda introduced the combined api. The combined api is statically defined, which solves the performance problem that the hook
must regenerate the temporary closure function every time it is rendered. In the hook
, the old value trap, manual detection dependence and other coding experience problems are solved.
However, react is the coding method of all in js, so as long as we dare to think and do, all excellent programming models can be absorbed. Next, we use native hook
and concentrated setup
and pass Examples and explanations, to completely solve the pain point of hook
mentioned by You Da^_^
react hook
We first design a traditional counter, the requirements are as follows
- There is a decimal and a large number
- There are two groups of plus and minus buttons, which operate on decimal and large numbers respectively, the decimal button adds and subtracts 1, and the large number button adds and subtracts 100
- Pull the welcome greeting when the counter is first mounted
- When the decimal reaches 100, the button turns red, otherwise it turns green
- When the large number reaches 1000, the button turns purple, otherwise it turns green
- When the big number reaches 10,000, the number of the big number is reported
- When the calculator is uninstalled, report the current number
In order to complete this requirement, we need to use the following 5 hooks
useState
After the requirement, we need to use the first hook useState
to initialize the state of the component's first rendering
function Counter() {
const [num, setNum] = useState(6);
const [bigNum, setBigNum] = useState(120);
}
useCallback
If you need to use the cache function, you need to use the second hook useCallback
, here we use this hook to define the addition and subtraction functions
const addNum = useCallback(() => setNum(num + 1), [num]);
const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]);
useMemo
If you need to use the cached calculation results, you need to use the third hook useMemo
, here we use this hook to calculate the button color
const numBtnColor = useMemo(() => {
return num> 100?'red':'green';
}, [num]);
const bigNumBtnColor = useMemo(() => {
return bigNum> 1000?'purple':'green';
}, [bigNum]);
useEffect
To handle the side effects of the function, the fourth hook useEffect
is needed. Here we are used to deal with the two requirements
- When the big number reaches 10,000, the number of the big number is reported
- When the calculator is uninstalled, report the current number
useEffect(() => {
if (bigNum> 10000) api.report('reach 10000')
}, [bigNum])
useEffect(() => {
return ()=>{
api.reportStat(num, bigNum)
}
}, [])
useRef
The writing of useEffect
using the cleanup function above will be warned in the IDE, because of the internal use of num, bigNum
variables (not writing dependencies will fall into the trap of the old value of the closure), so we are required to declare dependencies
However, if we change to the following method to avoid IDE warnings, obviously it is not our intention. We just want to report the number when the component is uninstalled, instead of triggering the cleanup function every round of rendering
useEffect(() => {
return ()=>{
api.reportStat(num, bigNum)
}
}, [num, bigNum])
At this time we need the fifth hook useRef
to help us fix our dependencies, so the correct way of writing is
const ref = useRef();// ref is a fixed variable, and each round of rendering points to the same value
ref.current = {num, bigNum};// Help us remember the latest value
useEffect(() => {
return () => {
const {num, bigNum} = ref.current;
reportStat(num, bigNum);
};
}, [ref]);
Complete counter
After using 5 hooks, our complete component is as follows
function Counter() {
const [num, setNum] = useState(88);
const [bigNum, setBigNum] = useState(120);
const addNum = useCallback(() => setNum(num + 1), [num]);
const addNumBig = useCallback(() => setBigNum(bigNum + 100), [bigNum]);
const numBtnColor = useMemo(() => {
return num> 100? "red": "green";
}, [num]);
const bigNumBtnColor = useMemo(() => {
return bigNum> 1000? "purple": "green";
}, [bigNum]);
useEffect(() => {
if (bigNum> 10000) report("reach 10000");
}, [bigNum]);
const ref = useRef();
ref.current = {num, bigNum};
useEffect(() => {
return () => {
const {num, bigNum} = ref.current;
reportStat(num, bigNum);
};
}, [ref]);
// render ui ...
}
Of course, we can abstract this code separately as a hook based on the customizable characteristics of hook
. In this case, we only need to export the data and methods, so that the Counter components expressed by multiple uis can be reused, while also achieving ui It is isolated from the business and facilitates maintenance.
function useMyCounter(){
// .... slightly
return {num, bigNum. addNum, addNumBig, numBtnColor, bigNumBtnColor}
}
concent setup
The hook
function must be re-executed during each round of rendering, so it is inevitable that a large number of temporary closure functions will be generated during each round of rendering. If we can omit them, it can indeed help reduce gc Some recovery pressure, now let's take a look at what the Counter will look like after using setup
.
Using concent
is very simple, just use the run
api to start it before the root component, so we don't have a module definition, just call it directly.
import {run} from'concent';
run();// Start first, in render
ReactDOM.render(<App />, rootEl)
Then we slightly modify the above logic, and wrap it all inside setup
. The logic inside the setup function will only be executed once. The APIs provided by the rendering context ctx
that need to be used include initState
, computed
, effect
, setState
, and the state state
that needs to be read when calling setState
are also obtained by ctx
.
function setup(ctx) {// rendering context
const {initState, computed, effect, state, setState} = ctx;
// setup is executed only once before the component is rendered for the first time, we can write related business logic internally
}
initState
initState
is used to initialize the state, instead of useState
, when our component state is large, we still don't need to consider how to divide the state granularity.
initState({ num: 6, bigNum: 120 });
The function initialization state is also supported here
initState(()=>({ num: 6, bigNum: 120 }));
computed
computed
is used to define the calculation function. When deconstructing from the parameter list, the input dependency of the calculation is determined. Compared with useMemo
, it is more direct and elegant.
// This calculation function is triggered only when num changes
computed('numBtnColor', ({ num }) => (num> 100?'red':'green'));
Here we need to define two calculation functions. The calculation function can be configured with the description body of the calculation object, so that you only need to call computed
once.
computed({
numBtnColor: ({ num }) => num> 100?'red':'green',
bigNumBtnColor: ({ bigNum }) => bigNum> 1000?'purple':'green',
});
effect
The usage of effect
is exactly the same as useEffect
, the difference is that only the key name is passed in the dependent array. At the same time, effect
internally encapsulates the life cycle of function components and class components. Make any changes and migrate to class components
effect(() => {
if (state.bigNum> 10000) api.report('reach 10000')
}, ['bigNum'])
effect(() => {
// Here you can write what needs to be done when the first rendering is completed
return () => {
// Cleanup function triggered when uninstalling
api.reportStat(state.num, state.bigNum)
}
}, []);
setState
It is used to modify the state. After we define the method in setup
based on setState
, then return. Then we can get these method handles through ctx.settings
in any component that uses this setup
Can call
function setup(ctx) {// rendering context
const {state, setState} = ctx;
return {// export method
addNum: () => setState({ num: state.num + 1 }),
addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
}
}
Complete Setup Counter
Based on the above apis, the logic code of our final Counter is as follows
function setup(ctx) {// rendering context
const {initState, computed, effect, state, setState} = ctx;
// Initialization data
initState({ num: 6, bigNum: 120 });
// Define calculation function
computed({
// When the parameter list is deconstructed, the input dependency of the calculation is determined
numBtnColor: ({ num }) => num> 100?'red':'green',
bigNumBtnColor: ({ bigNum }) => bigNum> 1000?'purple':'green',
});
// define side effects
effect(() => {
if (state.bigNum> 10000) api.report('reach 10000')
}, ['bigNum'])
effect(() => {
return () => {
api.reportStat(state.num, state.bigNum)
}
}, []);
return {// export method
addNum: () => setState({ num: state.num + 1 }),
addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
}
}
After defining the core business logic, we can use useConcent
to assemble our defined setup
inside any function component to use it. useConcent
will return a rendering context (and the parameter list of the setup function refers to Is the same object reference, sometimes we also call the instance context), we can get the target data and methods from ctx
on demand, for this example, we can export
You can use the three keys of state
(data), settings
(the method returned by the setup package), and refComputed
(container of the calculation function result of the instance).
import {useConcent} from'concent';
function NewCounter() {
const {state, settings, refComputed} = useConcent(setup);
// const {num, bigNum} = state;
// const {addNum, addNumBig} = settings;
// const {numBtnColor, bigNumBtnColor} = refComputed;
}
We mentioned above that setup
can also be assembled to class components, just use register
. Note that the assembled class components can be directly obtained from this.ctx
to the rendering context generated by concent
At the same time, this.state
and this.ctx.state
are equivalent, and this.setState
and this.ctx.setState
are also equivalent, which is convenient for users to change the code 0 to access concent
Use.
import {register} from'concent';
@register(setup)
class NewClsCounter extends Component{
render(){
const {state, settings, refComputed} = this.ctx;
}
}
Conclusion
Compared with native hooks, setup
fixes the business logic in a function that will only be executed once, provides a more friendly api, and is perfectly compatible with class components and function components, allowing users to escape the troubles of the use rules of hook
( Think about useEffect and useRef, is there a significant cognitive cost?), instead of passing these constraints on learning barriers to users, it is also more friendly to gc. I believe everyone has already defaulted to hook
yesAn important invention of react
, but in fact, it is not for users, but for frameworks. Users do not need to understand the details and rules of brain-burning. For concent users, they only need one hook to open one. The portal can implement all business logic in another space, and these logics can also be reused on class components.
My dear guest official has seen so much, don’t hurry up and try it out. The following links are provided in two ways for you to play with😀
one more thing
If you want to share the state of the two hook counters, we need to modify the code to connect to redux
or build a self-built Context
, but in the development mode of concent
, setup
does not require any modification, just declare one in advance Module, and then register the module that belongs to the component. This silky migration process allows users to flexibly deal with various complex scenarios.
import {run} from'concent';
run({
counter:{
state: {num:88, bigNum: 120 },
},
//reducer: {...}, // If the operation data process is complicated, the business can be promoted here
})
// For function components
useConcent({setup});
// ---> Change to
useConcent({setup, module:'counter'})
// For function components
@register({setup});
// ---> Change to
@register({setup, module:'counter'});
Finish
Previous articles
- Use concent in react, share state easily and enjoy exact update
- Redux, mobx, and concentrated feature competition, see how juniors play against seniors
❤ star me if you like concent ^_^
If you have any questions about concent, you can scan the QR code and join the group consultation or send email to me( zhongzhengkai@gmail.com ). We will try our best to answer questions and help you learn more 😀.
Top comments (0)