React Hooks & ReactAPIs大全

为什么使用Hook不用Class

  1. 在组件之间复用状态逻辑很难
    React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。如果你使用过 React 一段时间,你也许会熟悉一些解决此类问题的方案,比如 render props 和 高阶组件。但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。如果你在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管我们可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。

    你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。

    具体将在自定义 Hook 中对此展开更多讨论。

  2. 复杂组件变得难以理解
    我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。

    在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

    为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。

    我们将在使用 Effect Hook 中对此展开更多讨论。

  3. 难以理解的 class
    除了代码复用和代码管理会遇到困难外,我们还发现 class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。

    Hook 使你在非 class 的情况下可以使用更多的 React 特性。 从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hook 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。

类组件和函数式组件的区别

  • 在React中,你可以通过两种方式来创建组件:类组件(Class Components)和函数式组件(Functional Components)。随着React Hooks的引入,函数式组件的功能得到了极大的增强,使得它们能够执行几乎所有类组件能够做的事情。以下是类组件和函数式组件之间的一些主要区别:
    • 类组件
      语法:类组件使用ES6的类语法定义。
      状态管理:类组件可以使用this.state和this.setState来管理状态。
      生命周期方法:类组件可以使用生命周期方法(如componentDidMount、componentDidUpdate和componentWillUnmount)来执行代码。
      引用(Refs):在类组件中,你可以使用React.createRef来创建引用,并通过this.refs.refName来访问它们。
      this关键字:类组件中经常需要绑定事件处理程序或传递回调函数时使用this。
      简洁性:class 定义类相对function定义的执行起来比较臃肿,复杂度要高;
      export default class App extends React.Component {
        constructor(props) {
          super(props);
          this.state = { count: 0 };
        }
        componentDidMount() {}
      
        increment = () => {
          this.setState({ count: this.state.count + 1 });
        };
      
        render() {
          return <div onClick={this.increment}>{this.state.count}</div>;
        }
      }
      
    • 函数式组件
      语法:函数式组件使用普通的JavaScript函数或箭头函数来定义。
      Hooks:React 16.8引入了Hooks,使得函数式组件能够使用状态和其他React特性,如useState、useEffect。
      没有this关键字:函数式组件不使用this关键字,一切都通过函数的参数传递。
      简洁性:通常情况下,函数式组件比类组件更加简洁且易于理解。
      import React, { useState, useEffect } from "react";
      
      function MyComponent(props) {
        const [count, setCount] = useState(0);
      
        useEffect(() => {
          // 类似生命周期 componentDidMount, componentDidUpdate, componentWillUnmount
        }, []); //依赖数组,数组变化,重新执行
      
        const increment = () => {
          setCount(count + 1);
        };
      
        return <div onClick={increment}>{count}</div>;
      }
      

Hook简介

  • Hook简介
    • React Hooks 自 React 16.8 版本引入以来,已成为在函数组件中使用状态和其他React特性的标准方式。
  • 在 React 18 版本之前,基础的 Hooks 包括了以下几种:

    useState: 使你能够在函数组件中添加和管理状态。
    useEffect: 使你能够执行副作用操作,如数据获取、订阅或手动更改 DOM。
    useContext: 使你能够访问 React 的上下文(Context)API,从而在组件树中无需直接传递 props 就可以共享值。
    useReducer: 用于处理组件状态逻辑,尤其是复杂组件的状态逻辑,提供了一个类似 Redux 的reducer 函数。
    useCallback: 返回一个记忆化的回调函数,该回调函数仅在其依赖项改变时才会更新。
    useMemo: 返回一个记忆化的值,该值仅在依赖项改变时才会重新计算。
    useRef: 返回一个可变的 ref 对象,其.current属性被初始化为传入的参数(initialValue),返回的对象将在组件的整个生命周期内保持不变。
    useImperativeHandle: 自定义使用 ref 时公开给父组件的实例值。
    useLayoutEffect: 与 useEffect 相似,useLayoutEffect会先于useEffect执行。useLayoutEffect的执行时机紧随DOM更新之后同步调用,可以用于读取 DOM 布局并同步触发重渲染,而useEffect则等到浏览器完成渲染之后才会执行。
    useDebugValue: 用于在 React 开发者工具中显示自定义的 Hook 标签,方便调试。

  • React 18 版本引入了许多新特性,但核心的 Hooks API 并没有显著改变。
    • react-v18
    • React 18 引入的主要变化集中在并发特性上,如并发渲染(Concurrent Rendering),以及与之相关的新API如 useTransition 和 useDeferredValue。新的并发特性可以帮助开发者控制渲染的优先级,提高大型应用的性能和用户体验。例如:

    useId: 是一个用于生成唯一ID的钩子,它在服务器端渲染和客户端渲染之间保持一致,从而避免水合时的不匹配问题。它主要用于为DOM元素生成稳定、唯一的ID,例如

Hooks

基础 Hook
useState
useEffect
useContext

额外的 Hook
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
useDeferredValue [v18.0]
useTransition [v18.0]
useId [v18.0]
useSyncExternalStore [v18.0]
useInsertionEffect [v18.0]

useState

  • useState 的更新不是异步的,而是由于 React 的批处理和更新调度机制导致的
// useState:
function App(){
    // 会把useState传递的第一个参数赋值给state
    // 当改变state后,视图会更新;
    // 每一次更改数据,那么就会重新渲染组件,同时赋默认值的操作也会执行,只不过如果后面state已经有值,
    // 不再执行这个函数
    // let a =1
    let [state,setState]=useState(function(){
        // 初始值执行一次,后面更新不再执行;
        // console.log(99);
        return {
            m:0,
            n:0
        }
    })
    return <div>
        {state.m}{state.n}
        <button onClick={()=>{
            // 这个方法会将原来的值直接覆盖;
            setState({
                ...state,
                n:200
            })
        }}>+</button>
    </div>
}

// function App(){
//     // 会把useState传递的第一个参数赋值给state
//     // 当改变state后,视图会更新;
//     // 每一次更改数据,那么就会重新渲染组件,同时赋默认值的操作也会执行,只不过
//     console.log(100);
//     let [m,setStateM] = useState(0);
//     let [n,setStateN] = useState(0);
//     return <div>
//         {m}分
//         {n}
//         <button onClick={()=>{
//             // 这个方法会将原来的值直接覆盖;
//             setStateM(100);
//             setStateN(200);
//         }}>+</button>
//     </div>
// }

// function App(){
//     // 会把useState传递的第一个参数赋值给state
//     // 当改变state后,视图会更新;
//     let [state,setState] = useState({
//         m:0,
//         n:0
//     });
//     return <div>
//         {state.m}分
//         {state.n}
//         <button onClick={()=>{
//             setState({
//                 ...state,
//                 n:100
//             })
//         }}>+</button>
//     </div>
// }

// function App(){
//     let [num,changeNum] = useState(0);// [这就是定义状态的hook];
//     // [状态,改变状态的方法]
//     return <div>
//         {num}
//         <button onClick={()=>{
//             changeNum(num+1)
//         }}>+</button>
//     </div>
// }

useEffect

  • useEffect 跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
  • useEffect 第一个参数-副作用函数还可以通过返回一个函数来指定如何“清除”副作用。例如,使用副作用函数来订阅事件,并通过取消订阅来进行取消订阅。
function App(){
    let [state,setState]=useState(function(){
        return {
            m:0,
            n:0
        }
    })
    // 在设置钩子函数
    // useEffect:接收一个回调函数(副作用函数);这个函数不是立即运行的;设置函数的生命周期:在第一次解析挂载结构会默认一次,相当于componentDidMount ;当后期数据更新,数据更新,相当于componentDidUpdate;副作用函数如果返回一个函数,React 会在组件销毁时执行返回的函数,然后在后续渲染时重新执行副作用函数,相当于componentWillUnmount;
    useEffect(()=>{
        // 设置依赖项,只有n状态发生改变,才会执行这个钩子函数;
        console.log("ok1");
    },[state.n]);
    useEffect(()=>{
        console.log("ok");
    },[]);
    // console.log("hello");
    return <div>
        {state.m}{state.n}
        <button onClick={()=>{
            setState({
                ...state,
                m:200
            })
        }}>+</button>
    </div>
}

useRef

function App(){
    let  spanRef = useRef();// {current:undefined}
    console.log(spanRef);// 获取元素;
    return <div>
        <span ref={spanRef}>0</span>
        <button onClick={()=>{
            // 给哪个元素,那么这个对象就把这个元素赋值给对选哪个current属性
            //console.log(spanRef)
            spanRef.current.innerHTML++;
        }}>+</button>
    </div>
}
export default App;

useReducer

  • Reducer 可以整合组件的状态更新逻辑.
import React,{useState,useEffect,useRef,useReducer} from "react";// {useState,Component}

function reducer(state,action){
    // console.log(state)
    state = JSON.parse(JSON.stringify(state));
    switch(action.type){
        case "CHANGE_N":
           state.n++;
           break;
    }
    return state;
}

// class : this.props
function App(props){
    //console.log(this);  this 指向undefined;
    console.log(props);// props可以来接收行间的属性;这就是属性;
    let [{n,m},dispatch]=useReducer(reducer,{n:0,m:0});
    // dispatch派发动作让reducer元素,reducer运行,更改state;
    return  <div>
        {n}<br></br>{m}
        <button onClick={(ev)=>{
            // console.log(ev);// 事件对象
            dispatch({type:"CHANGE_N"})
        }}>+N</button>
        <button>+M</button>
    </div> 
}
export default App;

APIs

  • createContext
  • forwardRef
  • lazy
  • memo
  • startTransition
  • createPortal
  • flushSync

常见问题

useEffect 和 useLaoutEffect 区别

在React中,useEffect和useLayoutEffect都是用于在函数组件中执行副作用的钩子。它们之间的主要区别在于执行时机和场景。
useEffect是在组件渲染到屏幕上之后执行的,是异步的。它适用于数据获取、订阅或者手动修改React之外的DOM等场景。
useLayoutEffect是在DOM更新之后,浏览器绘制之前同步执行的。它适用于那些需要同步读取DOM布局,以确保持久性布局更新场景,比如避免布局抖动。
useLayoutEffect会先于useEffect执行。useLayoutEffect的执行时机紧随DOM更新之后,而useEffect则等到浏览器完成渲染之后才会执行。

  • 使用场景举例:
    如果你需要在浏览器绘制之前同步执行一些操作,比如根据DOM尺寸计算新的布局,应该使用useLayoutEffect。
    如果你需要进行数据获取、订阅或者在不关心它们对DOM布局影响的情况下修改DOM,应该使用useEffect。
    由于useLayoutEffect会阻塞浏览器渲染,所以应当尽量优先使用useEffect以避免不必要的性能问题。

延迟调用会存在作用域不一致

在延迟调用的场景下,一定会存在作用域不一致

使用 setTimeout、setInterval、Promise.then 等
useEffect 的卸载函数

函数组件的state是单独存的,没有保存在当前这个函数的闭包里。
给useEffect的deps传个空数组,它依赖的所有state值,都是它创建那一刻的,不会再更新,所以一直是初始状态

  1. 利用函数参数oldState 直接取 let oldState = hookStates[currentIndex];
  2. 通过 useRef 来保证任何时候访问的 countRef.current 都是最新的,以解决作用域不一致问题
import { useEffect, useState, useRef } from "react";

export default function Test() {
  const [count, setCount] = useState(1);

  //   const countRef = useRef(1);

  useEffect(() => {
    let timer = setInterval(() => {
      //1 
      setCount((count) => ++count);

      // 2 
      // countRef.current= ++countRef.current;
      // setCount(countRef.current)
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);

  return <div>number:{count}</div>;
}
---------------------------------------------------
const [count, setCount] = useState(0);

// 通过 ref 来储存最新的 count
const countRef = useRef(count);
countRef.current = count;

useEffect(() => {
  const timer = setTimeout(() => {
    console.log(countRef.current)
  }, 3000);
  return () => {
    clearTimeout(timer);
  }
}, [])

useState 适当合并(Form表单)

 const [formData, setFormData] = useState<any>({
    inputGroupSelect: 'RPC',
    inputGroupInput: '',
    productName: '',
    submittedBy: '',
    rangePicker: [],
    promoType: '',
    status: '',
  });
  
  //通过 ref 来储存最新的 formData
  const formDataRef = useRef(formData);
  formDataRef.current = formData;
  
  const { inputGroupSelect, inputGroupInput, productName, submittedBy, rangePicker, promoType, status } = formData;

  function search() {
  	//点击搜索触发方法
    const formData = formDataRef.current;
    console.log('form: ', formData);
  }

  function setForm(key: string, value: any) {
    //useState action 可以传函数
    setFormData((prevState) => {
      return { ...prevState, [key]: value };
    });
  }

const formBaseData = [{      
		onChange: (value) => {
		        setForm('submittedBy', value);
		},
		onClick: search
}];

Class组件 迁移到 Hook组件

生命周期方法要如何对应到 Hook?

constructor:函数组件不需要构造函数。你可以通过调用 useState 来初始化 state。如果计算的代价比较昂贵,你可以传一个函数给 useState。
getDerivedStateFromProps:改为 在渲染时 安排一次更新。
shouldComponentUpdate: React.memo.
render:这是函数组件体本身。
componentDidMount, componentDidUpdate, componentWillUnmount:useEffect Hook 可以表达所有这些(包括 不那么 常见 的场景)的组合。
getSnapshotBeforeUpdate,componentDidCatch 以及 getDerivedStateFromError:目前还没有这些方法的 Hook 等价写法.

列表滚动位置不对

  • 在 React 中,state 更新是排队进行的。
    • 强制 React 同步更新(“刷新”)DOM。 为此,从 react-dom 导入 flushSync 并将 state 更新包裹 到 flushSync 调用中,用 flushSync 同步更新 state
     function handleAdd() {
        const newTodo = { id: nextId++, text: text };
        flushSync(() => {
          setText('');
          setTodos([ ...todos, newTodo]);      
        });
        listRef.current.lastChild.scrollIntoView({
          behavior: 'smooth',
          block: 'nearest'
        });
      }
    

    子组件props改变默认值不刷新

    • 数据源dataSourceProp是从父组件传递到子组件的属性,并且在子组件内部被用作状态的初始值。
    • 需要注意的是,父组件传递的dataSourceProp只会在组件第一次渲染时设置初始状态。之后,即使父组件传递的dataSourceProp发生变化,也不会更新子组件的状态,除非你在子组件内部显式处理这种变化,比如使用useEffect钩子。
    function (props){
    	const { dataSourceProp} = props;
    	const [,]=usestate(dataSourceProp)
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值