美文网首页
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