美文网首页
useRef & useMemo & useCallback

useRef & useMemo & useCallback

作者: 隐号骑士 | 来源:发表于2020-08-30 17:15 被阅读0次

    useRef

    基本用法

    import { useRef } from 'react';
    function RefExample(props){
        const inputElement = useRef()
        return(<div>
            <input ref={inputElement}></input>
            <button onClick={()=>{
                inputElement.current.focus()
            }}>focus</button>
        </div>)
    }
    

    createRef 不同, 它不会每次渲染都新生成一个新的引用,而是会一直指向相同的引用

    trick1:用引用的方式让回调函数获得最新的状态

    function RefExample(props) {
        const [counter, setcounter] = useState(0)
    
        function handleClick() {
            setTimeout(() => {
                console.log(counter)
            }, 3000)
        }
        return (<div>
            <div>{counter}</div>
            <div onClick={handleClick}>delay show</div>
            <div onClick={() => {
                setcounter(counter + 1)
            }}>add</div>
        </div>)
    }
    
    function RefExample(props) {
        const counterUseRef = useRef()
        const [counter, setcounter] = useState(0)
    
        useEffect(() => {
            counterUseRef.current = counter
        })
        function handleClick() {
            setTimeout(() => {
                console.log(counterUseRef.current)
            }, 3000)
        }
        return (<div>
            <div>{counter}</div>
            <div onClick={handleClick}>delay show</div>
            <div onClick={() => {
                setcounter(counter + 1)
            }}>add</div>
        </div>)
    }
    

    trick2 :保存上次状态

    function RefExample(props) {
        const counterUseRef = useRef()
        const [counter, setcounter] = useState(0)
        useEffect(() => {
            counterUseRef.current = counter
        })
    
        return (<div>
            <div>{counter}</div>
            <div>{counterUseRef.current}</div>
            <div onClick={() => {
                setcounter(counter + 1)
            }}>add</div>
        </div>)
    }
    

    useMemo

    React 中没有 computed 这种属性,所以类似的计算属性需要手动维护
    Class中修改state导致render中的计算逻辑:

    class MemoExample extends React.Component {
        constructor() {
            super()
            this.state = {
                counter: 0,
                str: '1'
            }
        }
        render() {
            const doubleCounter = this.state.counter * 2
            return (<div>
                <div>{this.state.counter}</div>
                <div>{doubleCounter}</div>
                <div onClick={() => {
                    this.setState({ counter: this.state.counter + 1 })
                }}>add</div>
                <div>{this.state.str}</div>
                <div onClick={() => {
                    this.setState({ str: this.state.str + '1' })
                }}>add str</div>
            </div>)
        }
    }
    

    function中可以使用useMemo来避免不必要的计算:

    function MemoExample() {
        const [counter, setcounter] = useState(0)
        const [str, setStr] = useState('1')
        const doubleCounter = useMemo(() => {
            console.log('useMemo')
            return counter * 2
        }, [counter])
        return (<div>
            <div>{counter}</div>
            <div>{doubleCounter}</div>
            <div onClick={() => {
                setcounter(counter + 1)
            }}>add</div>
            <div>{str}</div>
            <div onClick={() => {
                setStr(str + '1')
            }}>add str</div>
        </div>)
    }
    

    useCallBack

    一个容易被忽略的细节:将函数直接写进子组件的props中会导致每次render都生成新的函数,导致子组件用shouldComponentUpdate或React.memo进行props检测的时候每次都会检测到新的参数变化,无法阻止事实上不必要的重新渲染

    class App extends React.Component {
        constructor() {
            super()
            this.state = {
                count: 1,
                name: 'jack'
            }
        }
        componentDidMount() {
            setInterval(() => this.setState(function (state, props) {
                return {
                    count: state.count + 1
                };
            }), 1000)
        }
    
        render() {
            return (
                <div>
                    <Child someProps={() => { console.log(123) }} name={this.state.name} />
                </div>
            );
        }
    }
    
    class Child extends React.PureComponent {
        render() {
            console.log('render') // 每秒钟渲染一次
            return (<div>
                {this.props.name}
            </div>)
        }
    }
    

    流行的一个优化技巧是:

    class App extends React.Component {
        constructor() {
            super()
            this.state = {
                count: 1,
                name: 'jack'
            }
        }
        componentDidMount() {
            setInterval(() => this.setState(function (state, props) {
                return {
                    count: state.count + 1
                };
            }), 1000)
        }
    
        somePropsCallback() { // 不要使用inline的方式书写props中的回调函数
            console.log(123)
        }
    
        render() {
            return (
                <div>
                    <Child someProps={this.somePropsCallback} name={this.state.name} />
                </div>
            );
        }
    }
    

    useCallback可以将函数缓存下来,只要依赖参数不发生变化,那么指向的都是同一个function:

    function App() {
        const [count, setCount] = useState(1)
        const [name, setName] = useState('jack')
        useEffect(() => {
            setTimeout(() => { setCount(count + 1) }, 1000)
        }, [count])
        const somePropsCallback = useCallback(() => {
            console.log(123)
        }, [])
        return (
            <div>
                {count}
                <Child someProps={somePropsCallback} name={name} />
            </div>
        )
    }
    class Child extends React.PureComponent {
        render() {
            console.log('render')
            return (<div>
                {this.props.name}
            </div>)
        }
    }
    
    

    一个问题

    困境
    function Form() {
      const [text, updateText] = useState('');
    
      const handleSubmit = useCallback(() => {
        console.log(text);
      }, [text]);
    
      return (
        <>
          <input value={text} onChange={(e) => updateText(e.target.value)} />
          <ExpensiveTree onSubmit={handleSubmit} /> // 很重的组件
        </>
      );
    }
    
    解决
    function Form() {
      const [text, updateText] = useState('');
      const textRef = useRef();
    
      useEffect(() => {
        textRef.current = text; // Write it to the ref
      });
    
      const handleSubmit = useCallback(() => {
        const currentText = textRef.current; // Read it from the ref
        alert(currentText);
      }, [textRef]); // Don't recreate handleSubmit like [text] would do
    
      return (
        <>
          <input value={text} onChange={e => updateText(e.target.value)} />
          <ExpensiveTree onSubmit={handleSubmit} />
        </>
      );
    }
    
    封装成自定义hook
    function Form() {
      const [text, updateText] = useState('');
      // Will be memoized even if `text` changes:
      const handleSubmit = useEventCallback(() => {
        alert(text);
      }, [text]);
    
      return (
        <>
          <input value={text} onChange={e => updateText(e.target.value)} />
          <ExpensiveTree onSubmit={handleSubmit} />
        </>
      );
    }
    
    function useEventCallback(fn, dependencies) {
      const ref = useRef(() => {
        throw new Error('Cannot call an event handler while rendering.');
      });
    
      useEffect(() => {
        ref.current = fn;
      }, [fn, ...dependencies]);
    
      return useCallback(() => {
        const fn = ref.current;
        return fn();
      }, [ref]);
    }
    

    相关文章

      网友评论

          本文标题:useRef & useMemo & useCallback

          本文链接:https://www.haomeiwen.com/subject/zussjktx.html