concept
Let’s start with the concept of side effects.
In computer science, a function side effect is an additional effect on the calling function that occurs when a function is called in addition to returning the value of the function – Wikipedia
A side effect is a change in the state of the system, or an observable interaction with the outside world, during the calculation of the result. – zhihu
You can see that both illustrate that the side effect is the effect on the external context in addition to the return value during the function call.
Side effects in life
The principles are quite abstract, but as the saying goes, programming comes from life. We can understand the side effects of programming analogy through the following examples from life.
-
Side effects of medicine: For example, when taking cold medicine, we aim to treat a cold. But due to the side effects of the drug, we developed a bad symptom of weakness.
Close analysis:
- Healing process – the execution of a function
- Take medicine — parameters
- Cure a cold – return value
- Weakness – Side effect
You can see the side effect of treating a cold is weakness. Then during the treatment process we will suffer from the side effect of physical weakness and become less efficient in doing other things, which is a bad side effect for us and we should avoid it.
-
Eat KFC: Suppose Hei has a KFC voucher. By noon he was so hungry that he looked for something to eat. I happened to have a KFC voucher in hand, and I used up a voucher while filling my stomach.
Close analysis:
- Filling up process – a function execution
- Eat KFC — parameters
- Full – results
- Use up a voucher – Side effect
As you can see, the side effect of filling up on KFC is to consume a voucher. Admittedly, that’s what we like to see happen: we get a discount as well as a full stomach. It’s a side effect that works to our advantage, in other words we take advantage of it.
Side effects in JS
Mapping from life to programming
Returning to the programming itself, we can use the KFC example to write the following code:
let voucher = 1; // Let isHungry = true; Function eat(shopName) {if(shopName === 'KFC' &&); // If (shopName === 'KFC' && voucher > 0) { voucher--; } return false; } isHungry = eat('KFC'); // Eat KFC onceCopy the code
Obviously, if we execute eat(‘KFC’) once; Then the coupon will be -1, which also conforms to the definition of having an effect on the environment when the function is called (except for the return value).
There are two ways to produce side effects in JS
Based on my personal understanding, I divide side effects in JS into two categories: a. Side effects caused by scoped chains and closures B. Side effects caused by closures Side effects of changing function entry parameters.
-
Side effects of scopes and closures.
Scopes and closures are some of the most interesting features in JS. Some of the programming features they bring are:
-
The function body can access variables declared in the external context in which it is defined (scope);
This is illustrated in the KFC example above (the eat function can be accessed to modify the voucher variable in the global scope).
-
When variables in a function are used indirectly from the outside, the context generated by the function execution is not destroyed (closures);
Here is an example of an iterator to illustrate the side effects of closures.
Next function createIterator(items) {let I = 0; Function next() {// Let done = I >= items.length; // return undefined if the array length is reached, otherwise return the next value let value =! done ? items[i++] : undefined; return { done: done, value: value }; } return { next }; } const iterator = createIterator([1,2]); iterator.next(); // "{ value: 1, done: false }" iterator.next(); // "{ value: 2, done: false }" iterator.next(); // "{ value: 3, done: true }"Copy the code
In this example, you can say that the next() function and I form a closure, which also has the side effect of incrementing next() I every time it is executed.
-
-
Side effects of changing function entry parameters
This should be true for most languages. When the input parameter of a function is a reference data type, we can change the attribute value of the data without affecting the external environment. Here’s a simple example to illustrate:
Const black = {name: 'black ', weight: 120, isHungry: true}; function eat(person) { person.weight++; return false; } black.isHungry = eat(black);Copy the code
The person.weight modification here is a side effect of the application data type of the input parameter.
Of course there is the “natural” example array method pop in JS. Here is an example of shifting the last of an array to the first:
Function lastToFirst(arr) {// const last = arr.pop(); arr.unshift(last); return arr; } function lastToFirstOptimize(arr) {const temp = [...arr]; const last = temp.pop(); temp.unshift(last); return temp; }Copy the code
Admittedly, in the first method it changes the array of the external environment, but the advantage is that if we really only need to change the array order, it is relatively convenient to omit the assignment step.
The second method does not change the original array but produces a changed order result. Both have advantages and disadvantages.
Js side effects summary
Js side effects are very special. The two types of side effects are essentially:
- The impact on the environment in which the function is declared;
- The impact on the environment in which the function is executed.
From the point of view of data manipulation functions, changing the data itself to be manipulated should allow side effects; Side effects should be avoided when raw data needs to be preserved. The handling and use of side effects is a matter of opinion, based on actual logic, which brings both a simple code style and a difficult determination of system state changes.
Side effects in Vue
Data driven view model
The only thing vUE and React have in common is a “data-driven view.” So in terms of side effects, their logical model looks something like this:
As can be seen from the data-driven view, the side effects that VUE can apply include computed, watch, etc., and React, useEffect, etc.
Of course, what these side effects usually do is change the data again to drive the view change. For example, vuE2 uses computed to correlate data multiples:
< the template > < div > < div > {{count}} < / div > < div > {{double}} < / div > < button @ click = "add" > count + 1 < / button > < / div > </template> <script> export default { data() { retrun { count: 1 } }, methods: { add() { this.count++; } } computed: { double() { return this.count * 2; } } } </script>Copy the code
In the code above, when we click on the button and call add to increase count, we have the side effect that double returns a double count, so computed during view changes is a side effect.
Model side effects of data changes
The above model, however, is not the most abstract. This is because computed, watch, useEffect, and view changes are all side effects of data changes.
So our model could look something like this:
reactivity API
Next, we will introduce the library REActivity API abstracted from the above side effect model in VUe3. It can run independently in the browser and Node environment, and can do its own view rendering by defining the render function based on its side effect principle.
Code examples:
const { reactive, effect } = require('@vue/reactivity'); const countRef = reactive({ value: 10 }); Effect () => {console.log(countref.value); }); countRef.value = 20; Countref. value = 30; countref. value = 30;Copy the code
You can see that console.log(countref.value) is triggered when effect is called and when data changes; This perfectly illustrates the side effects of the data changes shown above.
The result is as follows:
With the code above, it’s easy to figure out how to implement a data-driven view, as follows:
<body> <div id="app"> {{count}} <button onclick="add"> </button> </div> </body> reactive, effect } from './node_modules/@vue/reactivity/dist/reactivity.esm-browser.js'; // Define a simple render const app = document.getelementByid ('app'); const template = app.innerHTML; function render() { const btnIdEventMap = {}; Const newHtml = template.replace(/\{\{(\w+)\}\}/g, (match, propName)=>{return data[propName]; }) / / for branding elements with click event id. The replace (/ onclick = \ "\" (\ w +)/g (the match, eventName)=>{ const eventCount = Object.keys(btnIdEventMap).length; btnIdEventMap[`btn${eventCount}`] = eventName; return match + ` id="btn${eventCount}" `; }); // Re-render app.innerhtml = newHtml; For (const btnId in btnIdEventMap) {console.log(window[btnIdEventMap[btnId]]); console.log(document.getElementById(btnId)); document.getElementById(btnId).onclick = window[btnIdEventMap[btnId]]; Const data = reactive({count: 0}); window.add = () => { data.count = data.count + 1; } effect(render); // Call render function to render view </script>Copy the code
React side effects (useEffect)
First of all, a brief understanding of the above two concepts and uses.
UseState is a Hook (we’ll get to what that means in a moment). Add some internal state to the component by calling it from within the function component. React will retain this state for repeated rendering. UseState returns a pair of values: the current state and a function that lets you update it, which you can call in an event handler or some other place.
In short: useState provides the data for rendering the view once and methods for changing the data.
UseEffect is an Effect Hook that gives function components the ability to manipulate side effects. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in the Class component, but has been consolidated into an API.
In a nutshell: useEffect provides the side effects of state changes and “events” such as component uninstallation.
Let’s do it with a concrete example.
The first is useState:
const [count, setCount] = useState(0); Const [greet, setGreet] = useState(' greet! '); const setCountAndGreet = () => { setCount(10); setGreet('welcome! '); } console.log(' trigger render '); Return (<div> {count} -- {greet} <button onClick={() => setCount(1)} > </button> <button onClick={() => </button onClick={setCountAndGreet} > </button> </button> </div>)}Copy the code
Let’s analyze the results of clicking these three buttons
- Button 1: Call
setCount
Then React produced oneSide effects of rendering views, re-execute the function we defined to print once ‘trigger render’. - Button 2: Call
setCount
The React internal checks that the new value is the same as the old valueSide effects of rendering viewsWill not print ‘trigger render’. - Button 3: When a macro task (click event) triggers multiple sets at the same time, an action is recorded to the corresponding new state object and an action queue is formed. After entering the microtask, we rebuilt the React-Element based on the state changes (i.e., the result of re-executing the function we defined), processed it to generate a new Fiber tree, and finally updated the view.
UseEffect execution time:
const App = () => { const [count, setCount] = useState(0); Const [greet, setGreet] = useState(' greet! '); const countInDOM = document.getElementById('count')? .innerHTML; Console. log(' count is ${countInDOM} 'when the function fires); useEffect(() => { const countInDOM = document.getElementById('count')? .innerHTML; Console. log(' effect triggers when count is ${countInDOM} '); }); UseEffect (() => {console.log(' side effect that triggers greeting changes '); }, [greet]); Return (<div> <div id="count" >{count}</div> <div>{greet}</div> <button onClick={() => setCount(4)} > button 4</button> <button onClick={() => setGreet('welcome! </button> </div>)}Copy the code
Click button 4 alone and the result is as follows:
- Prints’ count is 0 when the function is fired ‘;
- Render view;
- Print ‘effect ‘if count is 4’, this operation does not change greet and therefore does not trigger the corresponding side effect.
Click button 5 alone and the result is as follows:
- Prints’ count is 0 when the function is fired ‘;
- Render view;
- Print ‘Side effects that trigger greeting changes’;
Therefore, we can simply analyze the side effects of REACT after data changes:
- Re-execute the function we defined (generating a new closure);
- Rebuild the Fiber tree;
- Trigger render view;
- Trigger an effect defined as a side effect;
conclusion
From the analysis of the two front-end frameworks, we can see that their coding styles are so similar, and their internal implementations are so different that all roads lead to Rome.
The use of side effects is definitely a boon to front-end coding because it helps us implement the idea of data-driven views without the tedious DOM manipulation in most cases. In today’s era of front-end frameworks, side effects can be said to exist all the time in the daily front-end coding, which plays a double-edged sword role. Both designers and users of the framework should ensure that side effects are clearly traceable, so as to make our code under control.