doubleyong
管理员
管理员
  • 最后登录2022-01-18
  • 发帖数1052
  • 最爱沙发
  • 喜欢达人
  • 原创写手
  • 社区居民
  • 忠实会员
阅读:395回复:0

[react]react Hooks 总结

楼主#
更多 发布于:2021-11-29 09:40
Hook 是 React 16.8 的新增特性,它可以让我们在不编写 class 的情况下使用 state 以及其他的 React 特性 (比如生命周期)。


Hook 出现之前,组件之间复用状态逻辑很难,解决方案(HOC、Render Props)都需要重新组织组件结构, 且代码难以理解。在React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”


组件维护越来越复杂,譬如事件监听逻辑要在不同的生命周期中绑定和解绑,复杂的页面componentDidMount包涵很多逻辑,代码阅读性变得很差


class组件中的this难以理解,且class 不能很好的压缩,并且会使热重载出现不稳定的情况


所以hook就为解决这些问题而来:
  • 避免地狱式嵌套,可读性提高。
  • 函数式组件,比 class 更容易理解。

  • class 组件生命周期太多太复杂,使函数组件存在状态。
  • 解决 HOC 和 Render Props 的缺点。

  • UI 和 逻辑更容易分离。



使用说明:
  • 只能在「函数最顶层」调用 Hook,不要在循环、条件判断或者子函数中调用;
  • 只能在 「React 的函数组件」中调用 Hook,不能是普通函数 也不能是 class 组件。



1.1 useState()

用于定义函数组件的 State。
useState 接受唯一的参数作为 state 的初始值,不设置则为 undefined。
useState 调用后返回一个数组,包含两个元素,第一个值为 state 读取值,第二个值为改变 state 的函数


// 使用 Hook 的函数组件
import { useState } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}> Click me </button>
    </div>
  );
}
// 等价的 class 组件
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

useState 的参数可以是一个函数:
const [counter, setCount] = useState(() => 10) // 这里只会在初始化的时候执行
setCount 的参数也可以是一个函数:
setCount(prevState => prevState + 10) // 累加

// 这里面 setCount 方法与 class 中的 setState 不同在于,此 setCount 不会合并 state 中的值

如果想要在 state 中使用一个对象需要在更新值的时候把之前的值解构出来:
const [state, setState] = React.useState({
  count: 0,
  greeting: "Hello, World!",
});

// ...
setState({
  ...state,
  count: state.count + 1
})

注:如果需要定义多个 state 只需要多次调用 useState 方法就行。



1.2 useEffect(callback, [source])


可以看做 class 组件中 componentDidMount、componentDidUpdate、和 componentWillUnmount 几个生命周期的大集合。给函数组件增加了操作副作用的能力(网络请求、更新 DOM、事件的监听)

useEffect 接受两个参数,第一个参数是一个 callback,可以用来做一些副作用比如异步请求,修改外部参数等行为。

import { useState, useEffect } from 'react';
function Example() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}> Click me </button>
    </div>
  );
}
如果 callback 函数中 return 了一个函数,这个函数会在组件卸载前执行,需要清除上次订阅的内容可以再这里面写。它会在调用一个新的 effect 之前对前一个 effect 进行清理,所以 effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。
 
useEffect(() => {
  console.log('订阅一些事件')
  return () => { // 组件卸载后执行 返回的函数
    console.log('取消订阅')
  }
})


useEffect 的第二个参数 source 是一个触发条件,数组类型,如果数组中的值变化才会触发 useEffect 的 callback 回调
  • 没有传入该参数时,默认在每次 render 时都会重新执行回调;
  • 传递一个 [],只有在组件初始化或销毁的时候才会触发;
  • 传入 [source] ,在依赖 state 发生改变时, 才重新执行该回调。


useEffect(() => {
  document.title = `You clicked ${count} times`;
}, []);




避免在 useEffect 中做太多事情,如果有多个依赖可能会发生变化时,直接分离 useEffect,会按照先后顺序执行。
function Post({ id, unlisted }) {
  useEffect(() => {
    fetch(`/posts/${id}`).then( )
  }, [id])
  useEffect(() => {
    setVisibility(unlisted)
  }, [unlisted])
}
使用 useEffect 传入依赖 时,有些依赖每次渲染都会重新生成一个引用,但是内部的值却没变,导致 effect 的无限更新。
// 如
const getDep = () => {
  return {
    foo: 'bar',
  };
};
useEffect(() => {
  // 无限循环了
}, [getDep()]);
// 解决方案:把依赖转为字符串
const getDep = () => {
  return {
    foo: 'bar',
  };
};
const dep = JSON.stringify(getDeps());
useEffect(() => {
  // ok!
}, [dep]);
// 最好还是用社区提供的方案:useDeepCompareEffect,它选用深比较策略



1.3 useLayoutEffect


跟 useEffect 函数签名一致,是在 DOM 修改后同步触发;并且总是比 useEffect 先执行,会阻塞浏览器的渲染。


useEffect 执行顺序: 组件更新挂载完成 -> 浏览器 dom 绘制完成 -> 执行 useEffect 回调。
useLayoutEffect 执行顺序: 组件更新挂载完成 -> 执行 useLayoutEffect 回调-> 浏览器dom绘制完成。


const [count, setCount] = useState(0);
useEffect(() => { // 会有闪烁
  console.log(`useEffect - count=${count}`)
  // 耗时的操作
  const pre = Date.now();
  while(Date.now() - pre < 1000) {}
  // count为 0 时重新生成个随机数
  if (count === 0) {    
    setCount(10 + Math.random() * 200);
  }
}, [count]);
useLayoutEffect(() => { // 不会有闪烁,但是会卡一下
  console.log(`useLayoutEffect - count=${count}`)
  const pre = Date.now();
  while(Date.now() - pre < 1000) {}
  if (count === 0) {    
    setCount(10 + Math.random() * 200);
  }
}, [count]);
<div onClick={() => setCount(0)}>{count}</div>
当在 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用 useLayoutEffect 替换 useEffect,否则可能会出现闪屏问题。


1.4 useRef
用于函数组件中获取 dom 元素。
import React, { useRef, useEffect } from 'react'
const HooksComponent = () => { 
  const inputRef = useRef(null);
  useEffect(() => {
    console.log(inputRef.current.value);
  }, [])
  return <input ref={inputRef} />
}



  • 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
  • 返回的 ref 对象在组件的整个生命周期内保持不变
  • 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方
  • 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
  • useRef 类似于类组件的 this



总结文件:https://blog.csdn.net/aliven1/article/details/120344030


1.5 useImperativeHandle



forwardRef案例:
子组件:
const Foo = forwardRef((params) => {
   const inputRef = useRef();
   const onclick= (params) => {inputRef.current.focus();}
   return (
     <div>
     <input type="text" ref={inputRef }/>
     <button onClick={onclick}>聚焦</button></div>
  )
)



父组件:
const App= (params) =>{
const inputRef= useRef();
const onclick = (params) =>{
console.log(inputRef.current);
retun (
<div>
<Foo ref= {inputRef} />
<button onclick={onclick}>父组件</button></div>
)


父组件去使用子组件中的方法
export default function App() {
const hello = useRef(null)
const handler = ()=>{//拿到hello内部的方法
hello.current.add()
}
return (
   <div>
    <Button onclick={ handler}>触发Hello组件中的方法</Button>
    <Hello ref={hello}/>
   </div>
  )
}


useImperativeHandle


可以让你在使用 ref 时自定义暴露给父组件的实例值。useImperativeHandle 在当前组件 render 后执行。// 语法
useImperativeHandle(ref, createHandle, [deps])
// * ref:需要传递的 ref。
// * createHandle: 需要暴露给父级的方法。
// * deps: 依赖。


注意:
没有deps,每当rerender时,useImperativeHandle 都会执行, 且能拿到 state中最新的值, 父组件调用传入的方法也是最新。
依赖[],每当rerender时,useImperativeHandle 不会执行,且不会更新到父组件。
依赖传入的state值 [value], 达到想要的效果。
知识需要管理,知识需要分享
游客


返回顶部

公众号

公众号