React.memo(), useCallback(), useMemo()

김민경·2023년 2월 6일
0

FE(WEB) - React

목록 보기
9/13

🎈 Virtual DOM

  • React : does not know the web, just a library that manages components, states, props, context and etc.
  • React DOM : interface to the web, responsible for working with the real DOM

re-evaluating components !== re-rendering the DOM

  • if checking with console.log(), console.log() executes while
    if there is no change in the DOM, nothing changes in the DOM

virtual DOM diffing

  • virtual DOM : ReactDOM receives the differences(React only passes the changes) and then manipulates the real DOM

  • the component where you manage state or where you have props or context, and if those changes, that component will be re-evaluated and re-executed

  • if the parent component is re-evaluated, all its child components are also re-evaluated
    => this is unnecessary re-evaluation
    => how to optimize?

🎈 React.memo()

DemoOutput.js

const DemoOutput = (props) => {
	console.log('DemoOutput Running');
    return <MyParagraph>
    	{props.show ? 'This is new!' : ''}
        </MyParagraph>
}

export default ✨React.memo(DemoOutput)

React should look at the props that this component gets
and check the new value for all those props
and compare it to the previous value

only if the value of a prop changed
the component should be re-executed and re-evaluated

if the value of a prop doesn't change,
the child component (MyParagraph) isn't re-executed also

the costs of React.memo()

We don't use it to all components because of its costs

  • has to store the previous prop values
  • and has to make the comparison

=> can be a good choice to use...
if you have a huge component tree with a lot of child components
and on a high level in the component tree,
you can avoid unnecessary re-render cycles
for the entire branch of the component tree

=> would not be a good choice to use...
if it is for smaller apps

if you have a component where you know it's going to change or its props values are going to change with pretty much every re-evaluation of the parent component

because the component should re-render anyways,
you can save that extra comparison of the prop values

🎈 useCallback()

stores a function(array/object) when the component re-evaluates

App.js

import React, { useState, useCallback } from 'react';

// if the state chagnes, function App re-executes
// toggleParagraphHandler also regeneragtes in every execution 

const App = () => {
	const [showParagraph, setShowParagraph] = useState(false);
    const [allowToggle, setAllowToggle] = useState(false);
    
    const toggleParagraphHandler = ✨useCallback(() => {
    	if (allowToggle) {
        	setShowParagraph((prevShowParagraph) => !prevShowParagraph)
        } 
    }, [allowToggle]);
    
    const allowToggleHandler = () => {
    	setAllowToggle(true);
    }
    
     // ⭐ closures ⭐
     // functions in js are closures
     // when a function is defined
     // , which happens when the app function runs
     // javascript basically locks in(stores) all the variables
     // that are defined outside of the function that we are using
     
     // (in this code...)
     // when the App component is re-evaluated,
     // react will not recreate the function in useCallback()
     // therefore the allowToggle function is still the old one
     // , not the most recent one
     
     // so by putting allowToggle as dependencies,
     // we recreate a function in useCallback whenever allowToggle changes
     // because values being used in that function
     // that are coming from outside the function might have changed
    
    return(
    	<Button onClick = {allowToggleHandler}> 
        	Allow Toggling
        </Button>
    	<Button onClick = {toggleParagraphHandler}> 
        	Toggle Paragraph!
        </Button>
}

Button.js

// since function is not a primitive type, but a reference type,
// React(javascript) regards the regenerated function 
// as an unidentical one

// so when comparing previous function(toggleParagraphHandler)
// and an updated function(toggleParagraphHandler)
// it makes React.memo() useless
// ✨-> use useCallback()! for reference type(function)

export default ⭐ React.memo(Button)

🎈 Scheduling State updates

components & states

useState()

React creates a new state variable using the default value then memorizes to which component that belongs
the default value that is passed in useState essentially is only considered once, the first time the component is rendered

So when the component is re-evaluated, no useState is created
Instead React recognizes that it already has a state for the very component, and it simply updates that state as needed

Therefore React will only do state managing and updating, no reinitialize, unless the component was totally removed

state updates & scheduling

(a very much instant process)
before changing the actual state,
React schedules the state change
(postpones the state change)

because when a lot of other performance intensive tasks are going on at the same moment, react considers to have a higher priority for that performance
(ex. you have an input field where the user is able to type something)

this guarantees the state changing to process ordlerly

but because of scheduling,
state change is not necessarily executed immediately

since multipe scheduled state can change at the same time,
(multiple updates can be scheduled at the same time)
it is recommended that you use function form below

  • dealing with state updates that depends on the same state
// if you depend on the previous state snapshot 
// for updating the state...

// this can overcome the delay (though it would proceed very quickly)
// and ensures that you depend on the latest state
set~ ((prevState) => ...)
  • dealing with the state updates that depends on some other states
useEffect(()=> {
	const identifier = setTimeout(() => {
    	setFormIsValid(
        	emailIsValid && passwordIsValid
        )
    }
    , 500);

	return () => { 
    	console.log('clean-up');
        clearTimeout(identifier);
    };
}, [emailIsValid, passwordIsValid]);

state batching

const navigateHandler = (navPath) => {
	setCurrentNavPath(navPath);
    setDrawerIsOpen(false);
}

in this case, the component isn't re-evaluated twice

instead, if you have two state updates,
react will batch(bundle) these state updating together

🎈 useMemo()

App.js

import React, {useState, useCallback, ⭐ useMemo} from 'react';

function App() {
	const [listTitle, setListTitle] = useState('My List');
    
    const changeTitleHandler = useCallback(() => {
    	setListTitle('New Title');
    }, []);
    
    const listItems = ⭐ useMemo(() => [5, 3, 1, 10, 9], [])
    
    return (
    	<div>
        	// even though title doesn't change,
            // items might change per ever re-evaluation
            // since it is a reference type
            // using only React.memo() is insufficient
            // ✨ -> use useMemo()!
        	<DemoList 
            	title = {listTitle} 
                 items = {listItems} 
             />
            <Button onClick = {changeTitleHandler}> Change List Title </Button>
        </div>
    )
}

export default App;

DemoList.js

import React, { ⭐ useMemo } from 'react';

const DemoList = (props) => {
	// ❗ should not use props.items as dependencies
    // because it props may change
    
    const { items } = props;

	const sortedList = ⭐ useMemo(() => {
    	props.items.sort((a,b) => a-b);
    }, [items]);
    // ✨ a performance-intensive code (occupies a lot of time)
    // don't want to run this code every time
    // the entire component is re-evaluated
    
    // put items as dependencies
    // because if items array changes,
    // it should be sorted again
    
    return (
    	<div className = {classes.list}>
        	<h2>{props.title}</h2>
            <ul>
            	{sorted.map((item) => (
                	<li key = {item}>
                    	{item}
                    </li>
                 ))}
            </ul>
        </div>
    )
}

export default React.memo(DemoList);

usually useMemo() is used far less often then using useCallback()
because memorizing functions is much more useful,
and you need that more often than memorizing data

you essetially want to memorize data
if it would be performance-intensive
to recalculate something based on it

otherwise, it might not really be worth it
because of the costs of using useMemo

  • it occupies some memory
  • storing functionality also takes up some performance
profile
🏛️❄💻😻🏠

0개의 댓글