Preface
This article around the theme concent of setup
and react the five hooks to expand, now referred to setup
can not be separated composition api
this keyword, to be exact setup
by composition api
brought out an overview, and composition api
(combination api) And optional api
(optional api) Two ways of organizing code, I believe you vue3
, they can exist at the same time, it is not mandatory that you can only use which one, but combined api The two major advantages really make developers more inclined to use it instead of optional api.
- Package the reusable logic with function as the basic unit and inject it into any component to make the decoupling of view and business more elegant
- Let the businesses of the same function be placed more closely together without being separated, and improve the development and maintenance experience
The above two points are hook
in React. Then, compared to hook
, what other advantages does the combined api have? I believe that some friends already know when Youda introduced the combination api, the combination api is statically defined, which solves the hook
must regenerate the temporary closure function every rendering, and there is no trap of the old value of closure hook
, Manual detection dependence and other coding experience problems.
However, react is all in js , so as long as we dare to think and do, all excellent programming models can be absorbed. Next, we will use the native hook
and the setup
and use examples and explanations to thoroughly hook
mentioned by Youda^\_^
react hook
We are here to design a traditional counter, the requirements are as follows
- There is a decimal, 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 large number reaches 10000, the number of the large 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 finishing the requirements, 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 function
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
. Here we are used to deal with the two requirements
- When the large number reaches 10000, the number of the large 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)
}
}, []);
Hey, at this point, react newbies have been brought into the trap, that is, closure old value trap , the original value is submitted at the moment of uninstallation, and the useEffect
writing method of the cleanup function here will also be warned if it is written in the IDE. , Because the num, bigNum
variable is used internally, we are required to declare the dependency.
useRef
But if we change to the following way to avoid IDE warnings, it is obviously 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是一个固定的变量,每一轮渲染都指向同一个值
ref.current = {num, bigNum}; // 帮我们记住最新的值
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 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, and at the same time, ui and business can be reused. Isolation is conducive to maintenance.
function useMyCounter(){
// .... 略
return { num, bigNum. addNum, addNumBig, numBtnColor, bigNumBtnColor}
}
concent setup
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 save them, it can indeed help gc reduce some recycling setup
, now let’s take a look at the Count after the transformation is completed with 06125c04de6918
concent
is very simple to use run
, just use the 06125c04de6967 api to start before the root component, so we don't have a module definition, just call it directly.
import { run } from 'concent';
run();// 先启动,在render
ReactDOM.render(<App />, rootEl)
Then we will be more than a little transformation logic, all wrapped into setup
internal logic of the internal setup function will only be executed once, the need to use a rendering context ctx
API provided are initState
, computed
, effect
, setState
, in conjunction with setState
call It also needs to read the status state
, which is also obtained ctx
function setup(ctx) {// 渲染上下文
const { initState, computed, effect, state, setState } = ctx;
// setup仅在组件首次渲染之前执行一次,我们可在内部书写相关业务逻辑
}
initState
initState
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
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.
// 仅当num发生变化时,才触发此计算函数
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 we only need to call computed
once.
computed({
numBtnColor: ({ num }) => num > 100 ? 'red' : 'green',
bigNumBtnColor: ({ bigNum }) => bigNum > 1000 ? 'purple' : 'green',
});
effect
effect
usage and useEffect
is exactly the same, the only difference is dependent on an array of key names can only pass, while effect
internal components of the life cycle function and class components were unified package, users can migrate business without making any changes to it Class component
effect(() => {
if (state.bigNum > 10000) api.report('reach 10000')
}, ['bigNum'])
effect(() => {
// 这里可以书写首次渲染完毕时需要做的事情
return () => {
// 卸载时触发的清理函数
api.reportStat(state.num, state.bigNum)
}
}, []);
setState
Used to modify the state, we define the method based on setState
setup
, and then return, then we can use this setup
in any component, get these method handles ctx.settings
function setup(ctx) {// 渲染上下文
const { state, setState } = ctx;
return {// 导出方法
addNum: () => setState({ num: state.num + 1 }),
addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
}
}
Complete Setup Counter
Based on the above apis, our final counter logic code is as follows
function setup(ctx) {// 渲染上下文
const { initState, computed, effect, state, setState } = ctx;
// 初始化数据
initState({ num: 6, bigNum: 120 });
// 定义计算函数
computed({
// 参数列表解构时就确定了计算的输入依赖
numBtnColor: ({ num }) => num > 100 ? 'red' : 'green',
bigNumBtnColor: ({ bigNum }) => bigNum > 1000 ? 'purple' : 'green',
});
// 定义副作用
effect(() => {
if (state.bigNum > 10000) api.report('reach 10000')
}, ['bigNum'])
effect(() => {
return () => {
api.reportStat(state.num, state.bigNum)
}
}, []);
return {// 导出方法
addNum: () => setState({ num: state.num + 1 }),
addNumBig: () => setState({ bigNum: state.bigNum + 100 }),
}
}
After defining the core business logic, we can use useConcent
inside any function component to assemble our defined setup
to use it. useConcent
will return a rendering context (and the setup function parameter list refers to the same object reference , Sometimes we also call the instance context), we can ctx
on demand, for this example, we can export
state
(data), settings
(setup package return method), refComputed
(example calculation function result container) these three keys can be used.
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 setup
same class may be assembled to the assembly, use register
to, class components should be noted that after assembly, from this.ctx
directly on concent
generated for rendering context, while it this.state
and this.ctx.state
are equivalent , this.setState
and this.ctx.setState
are also equivalent, it is convenient for the user to change the code 0 to access concent
.
import { register } from 'concent';
@register(setup)
class NewClsCounter extends Component{
render(){
const { state, settings, refComputed } = this.ctx;
}
}
Concluding remarks
Compared with native hooks, setup
the business logic inside 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 hook
of the rules of 06125c04de6f9d (think useEffect) With useRef, does it have a significant cognitive cost?), instead of passing these constraints on learning obstacles to users, it is also more friendly to gc. I believe everyone has defaulted that hook
is an important invention react
In fact, it is not for users, but for frameworks. Users don’t need to understand the details and rules of brain-burning. For concent users, they only need a hook to open a portal, and then they can access another portal. All business logic is implemented inside the space, and these logics can also be reused on class components.
better to go through the eyes a hundred times than once by hand. The following are links to the two ways of writing. You must have some experience
If you want to appeal two hook Counter state share, we need to transform the code access redux
or self Context
, but concent
under development model, setup
without any transformation, only need a statement ahead of a module, and then in the registration component belonging to the Module is enough, 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: {...}, // 如操作数据流程复杂,可再将业务提升到此处
})
// 对于函数组件
useConcent({setup});
// ---> 改为
useConcent({setup, module:'counter'})
// 对于函数组件
@register({setup});
// ---> 改为
@register({setup, module:'counter'});
One more thing
If you are interested in building an admin site by concent, we also provide a sample site tntweb-admin for your reference. Thanks to wp2vite , it realizes the dual-engine drive capability that can be started by both vite and webpack. You only need 3 steps:
git clone git@github.com:tnfe/tntweb-admin.git
npm i
npm run vite
Of course, if you want to start with npm run start
connect, but it is recommended to use webpack to build it, that is, npm run build
.
In addition to the dual-engine driver, tntweb-admin
also has built-in features, such as real-time theme , tabs , 27 dynamic typesetting . At the same time, the front-end architecture itself is also welcome. The site, this section is still under development. After we have released more template pages and the micro front-end deployment mode document is ready, it will be opened to developers for sharing as soon as possible.
team
TNTWeb-Tencent news front-end team, TNTWeb is committed to the industry's cutting-edge technology exploration and the improvement of team members' personal capabilities. The latest high-quality content in the field of small programs and web front-end technology has been compiled for front-end developers, updated weekly✨, welcome to star, github address: https://github.com/tnfe/TNT-Weekly
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。