【React】 hook

上半年学了一遍hook,今天因为某些原因又去翻了一遍文档,发现文档更详细了,也加了一些新的hook,赶紧补一下

useState

// 声明与初始化
const [ state, setState ] = useState(initialState)// 如果初始化的逻辑比较复杂,可以传入一个函数,在函数中计算并返回初始的state
const [state, setState] = useState(() => {
    const initialState = someExpensiveComputation(props);
    return initialState;
})

// 更新值
setState(newState);
setState(prevState => prevState + 1);
setSatate(prevState => {
    return { ...prevState, {name: 'sugarMei'}};
})
  • useState指挥在组件初始化渲染中起作用,后续渲染会被忽略;
  • setState中,如果你的更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。

useEffect

常见的副作用有:改变 DOM、添加订阅、设置定时器、记录日志等,这些都可能产生莫名其妙的 bug 并破坏 UI 的一致性。

// 基本使用
useEffect(didUpdate);

// 清除effect, return的代码,会在执行下一个effect之前使用。
useEffect(() => {
    const subscription = props.source.subscribe();
    
    return () => {
        subscription.unsubscribe();
    }
})

// effect会在每轮组件渲染完成后执行,性能并不好,所以可以使用第二个参数,告诉它执行的时机
// 当props.source发生改变时再执行
useEffect(() => {
    const subscription = props.source.subscribe();
    
    return () => {
        subscription.unsubscribe();
    }
}, [props.source]);

// 如果想让useEffect只执行一次,就传入一个空数组
useEffect(() => {
    // todo
}, [])
  • useEffect的执行时机:在浏览器完成布局与绘制之后,传给useEffect的函数会延迟调用;
  • useEffect将在组件更新前刷新上一轮渲染的effect

useContext

Component中,我们会使用static contextType = MyContext 或者 <MyContext.Consumer>来获得MyContext.Provider中提供的value

现在在函数组件中我们可以使用useContext

const name = 'sugarMei';
// 创建
const NameContext = React.creatContext(name);

function Parent(){
    return (
    	<NameContext.Provider value="王花花">
        	<Son />
        </NameContext.Provider>
    )
}

function Son(){
    return <GraSon />
}
    
function GraSon(){
    const name = useContext(NameContext);
    return <h1>{name}</h1>
}

useReducer

useState的替代方案。假设我们的setState时需要有一段逻辑来判断之后再赋值:

let value = ''; 
switch(props.atcion){
   case 'name': value = 'sugarMei'; break;
   case 'age': value = 20; break;
   default: value = undefined; break;
}

state逻辑较复杂且包含多个子值,或者下一个state 依赖于之前的 stateuseReducer 会比 useState 更适用,

const [ state, dispatch ] = useReducer(reducer, initialArg, init);

当调用dispatch相当于 调用reducer的逻辑,而init这个函数,可以惰性地创建初始 state,也有利于在reducer重置state

function init(initialCount){
    return { count: initialCount };
}

function reducer(state, action){
    switch(action.type){
      case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
    }
}

const [state, dispatch] = useReducer(reducer, initialCount, init);

useMemo

import React, { useState } from 'react';

function App(){
  const [count, setCount] = useState(0);

  const renderTitle = () => {
    console.log('renderTitle');
    return  <h1>123</h1>;
  }

  return (
    <>
      <div>{renderTitle()}</div>
      <p>{count}</p>
      <button onClick={() => { setCount(count + 1)}}>+1</button>
    </>
  )
}

export default App;

每次修改count都会引发renderTitle被重新执行,可以使用useMemo进行优化。

//const renderTitle = () => {
//    console.log('renderTitle');
//    return  <h1>123</h1>;
// }

// 改写为
const renderTitle = useMemo(() => {
    console.log('renderTitle');
    return () => <h1>123</h1>;
}, [])

修改之后,修改count并不会重新执行renderTitle

总结useMemo缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,应用场景如需要计算的状态

useCallback

<button onClick={addCount}>+1</button>

每次点击的时候回修改页面上的count,引起页面渲染,而addCount没必要重新渲染,所以:

const addCount = useCallback(() => {
    setCount(count + 1)
}, [])

这样的话,addCount只有在第一次的时候会渲染,然后就被缓存起来,后期组件重新渲染,也不会再重新创建这个函数。

总结useCallback缓存的结果是函数,主要用于缓存函数,应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,会触发整个组件更新,一些函数式没有必要更新的,此时就应该缓存起来,提高性能,减少对资源的浪费。

useRef

一个作用还是获得组件元素

import React, { useRef } from 'react';

function App(){
  const inputEl = useRef(null);

  const handleFoucs = ()=> {
    inputEl.current.focus();
  }

  return (
    <>
      <input ref={inputEl} />
      <button onClick={handleFoucs}>聚焦</button>
    </>
  )
}

export default App;

另一个作用是方便地保存任何可变值:

在以下这段代码中,我们在开始的时候弄了一个定时器,然后等到count大于10的时候就清除定时器;但是每次count改变,页面都会重新渲染,于是time已经不是上一次保存那个time了,所以页面没有在大于10 的时候解除定时器。

import React, { useState, useRef, useEffect } from 'react';

function App(){
  const [ count, setCount ] = useState(0);
  let time = null;

  useEffect(() => {
    time = setInterval(() => {
      setCount(count => count + 1);
    }, 1000)
  }, [])

  useEffect(() => {
    if(count > 10){
      clearInterval(time);
    }
  })

  return (
    <>
      <button>count: {count}</button>
    </>
  )
}

export default App;

使用useRef来初始化time可以解决这个问题,通过.current保存可变的值,放置才页面重新渲染的时候值被初始化。

import React, { useState, useRef, useEffect } from 'react';

function App(){
  const [ count, setCount ] = useState(0);
  const time = useRef();

  useEffect(() => {
    time.current = setInterval(() => {
      setCount(count => count + 1);
    }, 1000)
  }, [])

  useEffect(() => {
    if(count > 3){
      clearInterval(time.current);
    }
  })

  return (
    <>
      <button>count: {count}</button>
    </>
  )
}

export default App;

总结

  • useRef可以获得DOM元素;
  • useRef.current相当于一个全局变量,每次都会保持render的最新状态,useState的值在每个rernder中都是独立存在的,类比于for循环中的letvar
  • useState值得更像会触发组件重新渲染,而useRef.current不会触发组件重新渲染。

useImperativeHandle

之前使用了useRef获得input标签,然后进行了聚焦。如果input是包裹在一个子组件内部,那么需要借助forwardRef:

import React, { forwardRef, useRef } from 'react';

const InputComponent = forwardRef((props, ref) => {
  return (
    <input ref={ref} />
  )
})

function App(){
  const inputEl = useRef(null);

  const handleFoucs = ()=> {
    inputEl.current.focus();
  }

  return (
    <>
      <InputComponent ref={inputEl} />
      <button onClick={handleFoucs}>聚焦</button>
    </>
  )
}

export default App;

官方建议useImperativeHandle和forwardRef同时使用,减少暴露给父组件的属性,避免使用 ref 这样的命令式代码

import React, { forwardRef, useImperativeHandle, useRef } from 'react';

const InputComponent = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }))

  return (
    <input ref={inputRef} />
  )
})

function App(){
  const inputEl = useRef(null);

  const handleFoucs = ()=> {
    inputEl.current.focus();
  }

  return (
    <>
      <InputComponent ref={inputEl} />
      <button onClick={handleFoucs}>聚焦</button>
    </>
  )
}

export default App;

useLayoutEffect

componentDidMountcomponentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用

useDebugValue

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

// 在开发者工具中的这个 Hook 旁边显示标签
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');

参考

useMemo和useCallback的区别及使用场景

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值