首页 > 其他分享 >React笔记-Hooks(九)(非常全面)

React笔记-Hooks(九)(非常全面)

时间:2023-05-22 20:14:54浏览次数:56  
标签:const 函数 Hooks 笔记 React num useState 组件 return

React笔记-Hooks(九)

Hooks

概念

React Hooks 的意思是 组件尽量写成纯函数 如果需要外部功能和副作用 就用钩子把外部代码"钩"进来

函数组件和类组件区别

  • 函数组件没有状态(state) 类组件有
  • 函数组件没有生命周期 类组件有(挂载-更新-销毁)
  • 函数组件没有this 类组件有
  • 函数组件更适合做UI展示 类组件更适合做复杂的业务逻辑组件

为什么纯函数组件逐渐取代类组件

React团队希望 组件不要变成复杂的容器 最好只是数据流的管道 开发者根据需要 组合管道即可

Hooks用法

useState() 状态钩子

纯函数组件没有状态,useState()用于设置和使用组件的状态属性

// Hooks是无状态的 所以用这种方式替换类组件中的状态(state)
const [state, setState] = useState(initialValue);
// state:初始的状态属性,指向状态当前值,类似this.state
// setState:修改状态属性值的函数,用来更新状态,小驼峰命名
// setState((currentState) => {]})可以传入一个函数 函数接收一个参数 用于存放当前state
// initialValue:状态的初始值,该值会赋给state

import { useState } from 'react';



function LearnHooks () {

    let a = 1;
    const [num, setNum] = useState(0);
    const [name, setName] = useState([{name : 'bob', age : 18}, {name : 'sam', age : 20}, {name : 'kitty', age : 22}]);

    const addNum = () => {
        setNum(num + 1)
        // setNum是异步操作 所以console.log(num)输出的是更新前的值
        console.log(num)
        a += 1;
        // 这里a永远输出为2
        // 原因是hooks重新渲染是自调用 每次都会重新把a设为1 然后执行 a + 1
        console.log(a)
    }

    console.log('LearnHooks渲染了')
    return (
        <div>
            <h1>学习hooks的userState</h1>
            <div>当前num : {num}</div>
            <button onClick={() => addNum()}>num + 1</button>
            <div>{name.map((item) => {
                    return <div key={item.name}>name : {item.name}, age : {item.age}</div>
                })}
            </div>
            <input type="text" onChange={ ({target : {value}}) => setName([{name : value}])}/>
        </div>
    );
    
}

export default LearnHooks;

useEffect() 副作用钩子

可以实现特定的功能 如异步请求

useEffect(() => {
    // 回调函数,其中是要进行的异步操作代码
    return () => {}
    // useEffect中的return语句可以用于清除effect产生的副作用。当组件卸载时,React会执行return语句中的函数,以清除effect产生的副作用。例如,如果在useEffect中订阅了一个事件,那么在return语句中取消订阅可以避免内存泄漏。
}, [array])
// [array]:useEffect执行的依赖,当该数组的值发生改变时,回调函数中的代码就会被执行
// 如果[array]省略,则表示不依赖,在每次渲染时回调函数都会执行
// 如果[array]是空数组,即useEffect第二项为[],表示只执行一次

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

function hook() {

  const [num, setNum] = useState(1)
  /**
   * 第一个参数是回调函数
   * 第二个参数是依赖项
   * 每次num变化时都会变化
   * 
   * 注意初始化的时候,也会调用一次
   */
  useEffect(() => {
    console.log("每次num,改变我才会触发")
  }, [num])


  return (
    <div>
      <button onClick={() => setNum(num + 1)}>+1</button>
      <div>你好,react hook{num}</div>
    </div>
  );
}

export default hook;

useLayoutEffect()

useLayoutEffect是React提供的一个Hook,与useEffect功能类似,但在组件更新DOM前执行,而不是之后。

与useEffect不同的是,useLayoutEffect会阻塞浏览器渲染,并立即同步执行副作用函数。这可以确保使用组件的代码看到的是最新的DOM布局,因为它们在组件挂载或更新时都会在渲染通道中优先处理。

// useLayoutEffect接收两个参数:一个副作用函数和一个依赖项数组。副作用函数中可以进行DOM操作、计算布局等任务,并且可以通过返回一个清除函数来清理副作用产生的任何资源。

useLayoutEffect(() => { 副作用函数执行逻辑 }, [ 依赖项 ])

// 1. 编写副作用函数:useLayoutEffect需要传递一个函数作为参数,该函数称为副作用函数。在这个函数里,你可以访问到DOM、执行异步操作或计算等其它操作,并考虑它们的收尾工作。当组件的props或state发生变化时,都将重新运行该函数。
import { useLayoutEffect } from 'react';

function useMyLayoutEffect() {
  // 执行DOM相关操作
  return () => {
    // 清理工作
  };
}


// 2. 将useLayoutEffect挂载到组件:要在组件中使用useLayoutEffect这个函数,只需要调用它即可,并把上一步编写的副作用函数作为第一个参数。

function MyComponent() {

  useLayoutEffect(useMyLayoutEffect, []);

  return <div>Hello, world!</div>;

}


// 给useLayoutEffect传递依赖项数组:和useEffect一样,useLayoutEffect的第二个参数是一个数组,其中包含在副作用函数中需要被“监视”的任何变量。当其中的变量发生更改时,useLayoutEffect将重新运行其副作用函数。

function MyComponent({ name }) {

  useLayoutEffect(() => {

    console.log(`MyComponent is mounted: ${name}`);

  }, [name]);

  return <div>Hello, {name}!</div>;
  
}

useContext() 共享状态钩子

可以共享状态,作用是进行状态的分发,避免了使用Props进行数据的传递

// 第一步:创建全局的Context
const AppContext = React.createContext([初始化参数])

// 第二步:通过全局的Context进行状态值的共享
<AppContext.Provider value={{ 属性名: 值 }}>
    <其他组件1 />
    <其他组件2 />
</AppContext>

// 第三步:使用context
const context = useContext(AppContext);
{context.name}

// 在 App.js 文件中
import React, { createContext, useState } from 'react';
import Child from './Child';

export const MyContext = createContext();

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

  return (
    <MyContext.Provider value={{ count, setCount }}>
      <div>
        <h1>Count: {count}</h1>
        <button onClick={() => setCount(count + 1)}>Increment</button>
        <Child />
      </div>
    </MyContext.Provider>
  );
}

// 在 Child.js 文件中
import React, { useContext } from 'react';
import { MyContext } from './App';

function Child() {
  const { count, setCount } = useContext(MyContext);

  return (
    <div>
      <h2>Child Component</h2>
      <h3>Count: {count}</h3>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
    </div>
  );
}

useMemo() 记忆钩子(值)

useMemo是React中的一个hook,用于优化组件的性能。它的作用是缓存函数的返回值,只有当依赖项发生变化时才重新计算。这样可以避免在每次渲染时都重新计算函数的返回值,从而提高组件的性能。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

// 第一个参数是一个回调函数,用于计算需要缓存的值
// 第二个参数是一个数组,用于指定依赖项。只有当依赖项发生变化时,才会重新计算memoizedValue的值。

import React, { useState, useMemo } from 'react';

function MyComponent() {
  const { a, setA } = useState(1);
  const result = useMemo(() => {
    // 只有当a发生变化时才会重新计算。这样可以避免在每次其他渲染时都重新计算结果,提高组件的性能
    return a * 2;
  }, [a]);

  const add = () => {
    setA(a + 1)
  }

  return (
    <div>
      <div>{result}</div>
      <button onClick={() => add()}>a+1</button>
    </div>
  );
}

useCallback() 记忆钩子(函数)

useCallback是React中的一个Hook函数,用于优化函数组件的性能。它的作用是返回一个记忆化的回调函数,当依赖项发生变化时才会重新生成新的回调函数。这样可以避免在每次渲染时都创建新的回调函数,从而提高组件的性能。

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

// 第一个参数是回调函数
// 第二个参数是依赖项数组。当依赖项数组中的任意一个值发生变化时,useCallback会重新生成新的回调函数。如果依赖项数组为空,则每次渲染都会返回同一个回调函数。


// 需要注意的是,useCallback返回的是一个记忆化的回调函数,而不是一个普通的函数。因此,如果需要在组件外部使用该回调函数,需要将其作为props传递给子组件。

// 举例:使用useCallback优化组件性能

import React, { useState, useCallback } from 'react';

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

  // 普通的回调函数
  const handleClick = () => {
    setCount(count + 1);
  };

  // 使用useCallback优化的回调函数
  const handleClickMemoized = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>普通的回调函数</button>
      <button onClick={handleClickMemoized}>使用useCallback优化的回调函数</button>
    </div>
  );
}

export default MyComponent;

useReducer() 行为钩子

useReducer是React中一个状态管理的Hooks,用于处理复杂的组件状态逻辑。它和useState类似,都是用于管理组件状态的,但是useReducer可以更好地处理复杂的状态逻辑,尤其是在多个状态相互影响的情况下会更加方便和清晰。


// 1. 定义初始状态(initial state)和reducer函数。reducer函数的作用是根据当前的状态和操作类型(action)来返回新的状态值。

const initialState = {
  count: 0,
};

function reducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return { count: state.count + 1 };
    case 'SUB':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}


// 2. 在组件中使用useReducer Hook,传入reducer函数和initial state参数,获取当前的state值和dispatch函数。

import React, { useReducer } from 'react';

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h2>Counter: {state.count}</h2>
      <button onClick={() => dispatch({ type: 'ADD' })}>+</button>
      <button onClick={() => dispatch({ type: 'SUB' })}>-</button>
    </div>
  );
}

useRef() 保存引用值

useRef是React中的一个hook,用于创建一个可变的引用,类似于在类组件中使用的this.refs。与useState不同,useRef返回一个可变的值,而不会触发重新渲染组件。

useRef可以用于保存任何可变值,例如DOM元素的引用、定时器的标识符、上一个渲染周期的状态等。它还提供了一个.current属性来访问保存的值。

使用场景

  1. 获取DOM元素的引用

  2. 保存上一个渲染周期的状态

  3. 在useEffect中访问最新的props和state值

  4. 保存定时器的标识符,以便在组件卸载时清除


// useRef创建的ref对象与组件生命周期不相关,因此它不会在调用setState或props更新时自动更新,ref改变取决于使用它的具体方式

const ref = useRef()

// 因为hooks重新渲染其实是组件的自调用,所以我们不能在组件中直接定义一个值,let a = 1 或 const arr = []都是错误的,此时我们使用useRef来保存和访问持久性数据

useImperativeHandle()

useImperativeHandle是React Hook中的一个函数,用于在使用ref时,向父组件暴露子组件的方法或属性。它可以覆盖默认情况下通过ref自动公开该组件实例的方式,从而更加精准的控制哪些内容公开给父组件。

useImperativeHandle接受两个参数:ref对象和一个callback函数。callback函数应该返回包含想要挂载到ref上的任何公共方法或属性的对象。当父组件从ref调用该方法或访问该属性时,callback函数定义的逻辑将被执行。

// 1. 在子组件中使用forwardRef高阶组件转发ref,以在父组件中获得对子组件的引用。

import React, { forwardRef } from 'react';

const Child = forwardRef((props, ref) => {
  // 组件...
});

// 2. 使用useImperativeHandle Hook,将可供父组件访问的方法或属性包装在一个callback函数中,并将该函数作为useImperativeHandle的第二个参数传递

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

const Child = forwardRef((props, ref) => {
  const someMethod = () => {
    console.log('Hello from the child component');
  };

  useImperativeHandle(ref, () => ({
    someMethod,
  }));

  return <div>...</div>;
});

export default Child;

// 3. 在父组件中使用ref来调用从子组件暴露的方法

import React, { useRef } from 'react';
import Child from './Child';

function Parent() {
  const childRef = useRef(null);

  const handleClick = () => {
    // 调用子组件中公开的 someMethod 方法
    childRef.current.someMethod();
  };

  return (
    <div>
      <button onClick={handleClick}>调用子组件方法</button>
      <Child ref={childRef} />
    </div>
  );
}

其他

Immutable Data(不可变数据)

解决的问题

在 js 中,对象都是引用类型,在按引用传递数据的场景中,会存在多个变量指向同一个内存地址的情况,如果有多个代码块同时更改这个引用,就会产生竞态

实现的原理

Persistent Data Structure(持久化数据结构):用一种数据结构来保存数据。当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费,也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免 deepCopy把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享)

redux / flux

// redux / flux 要求采用返回新对象的形式,来触发数据更新、re-render,一般推荐的做法就是采用对象解构的方式。如果 state 对象巨大(注意:对象巨大),在结构、拷贝 state 的过程中,耗时会较长。

return {
  ...state,
  settings: {
    ...state.settings,
    profile:{
      ...state.settings.profile,
      darkmode: true,
    }
  }
}

immer

immer中文网址


// 开源库实现思路:原始对象先做了一层 Proxy 代理,得到 draftState 传递给 function。function(带副作用) 直接更改 draftState,最后 produce 返回新的对象

// 安装
npm install immer

//使用
import React, { useState } from "react";
import produce from "immer";


export default function App() {
  const [list, setList] = useState([1, 2, 3]);

  const addMutable = () => {
    list.push("新数据");
    setList(list);
  };

  const addImmutable = () => {
    /**
     * 第一个参数是要代理的数据
     * 第二个参数是一个函数
     */
    const newVal = produce(list, draft => {
      /**
       * draft 相当于 list
       * 在这个方法里面,可以直接修改draft,注意draft也只能在这个方法里面修改
       * 不需要返回值,immer内部已经帮我处理好了
       */
      draft.push('新数据')
    })
    console.log(newVal)
    setList(newVal);
  };

  return (
    <div className="App">
      <button onClick={addMutable}>已可变的方式添加</button>
      <button onClick={addImmutable}>已不可变的方式添加</button>
      {list.map((item, index) => (<li key={index}>{item}</li>))}
    </div>
  );
}

函数组件传值

hooks直接通过props传值

//父组件中在子组件标签上定义属性
import 子组件
<Son 属性={值}></Son>

// 子组件接收父组件中传递属性
props.属性
父传子

// 父组件
import { useState } from 'react'
import Son from './son'

function Father() {


  const [data, setData] = useState(0)

  return (
    <div>
      <Son
      {/*通过定义属性传值*/}
      name = 'bob' d = {data}
      ></Son>
      <button onClick={() => setData(data + 1)}>+1</button>
    </div>
  )

}

export default Father


// 子组件
import { useState } from 'react'


function Son (props) {

  return (
    <div>
      <div>{props.name}</div>
      <div>{props.d}</div>
    </div>
  )
}



export default Son

子传父

// 父组件
import { useState } from 'react'
import Son from './son'

function Father() {


  const [data, setData] = useState(0)

  const getSon = (msg) => {
    console.log(msg)
  }

  return (
    <div>
      <Son
      {/*定义一个方法用于接收子组件传值*/}
      name = 'bob' d = {data} giveFather={(msg) => getSon(msg)}
      ></Son>
      <button onClick={() => setData(data + 1)}>+1</button>
    </div>
  )

}



export default Father


// 子组件
import { useState } from 'react'


function Son (props) {

  // 接收父组件方法 通过此方法传值给父组件
  const set = () => {
    props.giveFather('儿子给父亲的')
  }


  return (
    <div>
      <div>{props.name}</div>
      <div>{props.d}</div>
      <button onClick={set}>给父亲</button>
    </div>
  )
}



export default Son

React.memo

一个高阶组件,用于优化React组件的性能。它可以帮助我们避免不必要的渲染,从而提高应用程序的性能。当组件的props没有改变时,React.memo会使用之前的渲染结果,而不会重新渲染组件。这对于那些渲染开销较大的组件特别有用。

import React from 'react';

const MyComponent = React.memo(props => {
  // 组件代码
}, (prevProps, currentProps) => {
  
  // prevProps 上次props
  // currentProps 当前props

  return Boolean;
  // false 渲染
  // true 不渲染
});

// 在上面的代码中,我们将一个函数组件传递给React.memo(),并将其返回的新组件赋值给MyComponent。现在,MyComponent将只在其props发生更改时重新渲染。

// 需要注意的是,React.memo()仅检查props的浅层比较。如果props包含复杂的对象或函数,可能需要手动实现更深层次的比较。
import React, { useState } from "react";

// 子组件
const SonMemo = React.memo(
  // 第一个参数 接收一个hook
  (props) => {
    return (
      <div>{props.data}</div>
    )
    // 第二个参数 接收一个函数
  }, (prevProps, currentProps) => {
    // 偶数不渲染
    // 奇数渲染
    return currentProps.data % 2 === 0
  }
)


function FatherMemo () {

  const [num, setNum] = useState(0)

  return (
    <div>
      <h1>{num}</h1>
      {/*点击加一*/}
      <button onClick={() => setNum(num + 1)}>按钮</button>
      <SonMemo data={num}></SonMemo>
    </div>
  )

}


export default FatherMemo;

React hooks中的过期闭包问题

什么是闭包

过期闭包概念

过期闭包(stale closure)是指一个闭包在创建之后,所引用的外部作用域内的变量已经被修改,但闭包内仍然保存了旧值。这就导致闭包中的代码与外部作用域内的实际状态不一致,从而造成错误的结果。

useEffect中过期闭包体现和解决

// react hook中useEffect的过期闭包
import { useEffect, useState } from "react"


function ExpiredClosure () {

  const [num, setNum] = useState(0)

  useEffect(
    () => {
      setInterval(() => {
        // 这里的num在初始化useEffect执行时取到0之后 因为闭包num值不会自动更新
        console.log(num)
      }, 2000)
    }, []
  )

return (
  <div>
    <h1>{num}</h1>
    <button onClick={() => setNum(num + 1)}>+1</button>
  </div>
)

}


export default ExpiredClosure


// 解决react hook中useEffect过期闭包问题
import { useEffect, useState } from "react"


function ExpiredClosure () {

  const [num, setNum] = useState(0)

  useEffect(
    () => {
      const timer = setInterval(() => {
        console.log(num)
      }, 2000)
      return () => {
        // 当组件卸载时 清除计时器
        clearInterval(timer)
      }
      // 添加num为依赖项
    }, [num]
  )

return (
  <div>
    <h1>{num}</h1>
    <button onClick={() => setNum(num + 1)}>+1</button>
  </div>
)

}


export default ExpiredClosure

useState中过期闭包体现和解决

// react hook中useState的过期闭包
import { useState } from "react";


function ExpiredClosure2 () {

  const [num, setNum] = useState(0)


  // 点击正常+1
  const add = () => {
    setNum(num + 1)
  }
  
  // 假设点击时num为3 两秒+2 = 5 在两秒之间不管点击多少次+1操作num变为678910...最后num都为5
  const add2 = () => {
    setTimeout(() => {
      setNum(num + 2)
    }, 2000)
  }

  // 当我们点击+2时候会取得当前值 之后点击其他改变num值 +2中的num都不会随之改变 两秒后取得的num+2给setNum后 渲染页面

  return (
    <div>
      <h1>{num}</h1>
      <button onClick={add}>+1</button><br />
      <button onClick={add2}>+2</button>
    </div>
  )

}


export default ExpiredClosure2;


// 解决react hook中useState过期闭包问题
import { useState } from "react";


function ExpiredClosure2 () {

  const [num, setNum] = useState(0)



  const add = () => {
    setNum(num + 1)
  }

  const add2 = () => {
    setTimeout(() => {
      // setNum中可以传入一个函数 这个函数接收一个参数 用于获取当前num值
      setNum((currentNum) => currentNum + 2)
    }, 2000)
  }

  return (
    <div>
      <h1>{num}</h1>
      <button onClick={add}>+1</button><br />
      <button onClick={add2}>+2</button>
    </div>
  )

}


export default ExpiredClosure2;

标签:const,函数,Hooks,笔记,React,num,useState,组件,return
From: https://www.cnblogs.com/SpicyPeper/p/17421590.html

相关文章

  • C#学习笔记 -- 抽象、密封类、静态类、扩展方法
    1、抽象成员指设计被覆写的函数成员,特征如下必须是一个函数成员,字段常量不可用属性、索引器、事件、方法都可用必须用abstracet修饰不能实现,代码、访问器用分号表示abstractpublicvoidPrintStuff(strings);​abstractpulbicintMyProperty{......
  • C#学习笔记 -- 构造器、类的访问修饰符
    构造函数的执行要创建对象的基类部分,需要隐式的调用基类的无参构造函数继承层次链中的每个类在执行他自己的构造函数体之前执行他的基类构造函数对象构造的顺序如下注意禁止在构造函数中调用虚方法在执行基类的构造函数,基类的虚方法会调用派生类的覆写方法,......
  • C#学习笔记 -- 成员访问修饰符
    成员访问修饰符所有显式声明在类声明中的成员都是互相可见的,无论给他们声明访问等级继承的成员不在类声明中显式声明,所以,继承成员对派生类的成员是可见的,也可以是不可见的,基类私有派生类看不见访问级别publicprivateprotectedinternalprotected......
  • 扩展可能性:发挥React Native与小程序集成的优势
    ReactNative是一个强大的前端跨端框架,可以帮助开发者高效地构建移动应用程序,并充分利用跨平台开发的优势,同时提供接近原生应用程序的性能和用户体验。它具有许多技术上的优势:跨平台开发:使用ReactNative,您可以使用相同的代码库构建同时运行在iOS和Android平台上的应用程序。......
  • sqli-lib通关笔记
    因为好久都没有联系过SQL注入了,打算重新拾起渗透方向的能力,去他妈的运维,老子才不要做运维,被傻逼公司给骗了,当了一年的运维,白白浪费了一年。第一关先查看一下代码: 真正的关于注入的核心语句就只有中间的select查询语句,一是先看是什么闭合,第二再看有没有过滤没有任何的......
  • (笔记)运放电路中并联反馈电容与反馈电阻的工作原理
     一、反馈电容运放反馈端电容并电阻,或许很多人都有疑惑;不同频率的信号经过电容都会产生不同程度的相移和衰减。 如果你利用的是其衰减,那么就是滤波。 如果你利用的是其相移,那么就是补偿。  (图一:反馈电容/电阻示意图)CF的作用:相位补偿,防止振荡,抑制高频噪声:一般来说,因为布......
  • 《程序员修炼之道》笔记3
     最后,具体到实际问题,当我们在编程时,项目开始之前,应该注意一些什么呢?                本书作者郑重提倡开始编程之前,请深思熟虑,不要靠巧合编程,所谓巧合编程,就是不加思索,接到任务开始coding,run一下,正常运行了,甚至于自己都不清楚它为什么能运行,作者批注这是由于......
  • 读书笔记 软件需求模式
    前面了解到什么事需求的,接下来就应该学习如何使用和编写需求模式。我们不仅到了解需求模式的含义,更要学会在什么情况下使用需求模式。在定义系统期间,有两种场合使用需求模式:1.当定义需求时,看是否存在一个模式可以指导如何定义这种需求。2.当考虑系统需求是否完全时,浏览主题覆盖......
  • 树状数组学习笔记
    树状数组(BinaryIndexedTree)是一种利用数的二进制特征进行检索的树状结构。树状数组是一种奇妙的数据结构,不仅非常高效,而且代码及其简洁。 #definelowbit(x)((x)&-(x))voidadd(intx,intd){//更新while(x<=n){tree[x]+=d;x+=lowbit(x);}}......
  • 《程序员修炼之道》笔记1
       首先不得不说这是一本熔知识,哲理,幽默与实践与一炉的奇书,引导你领悟程序设计的真谛,只可惜我没能好好的理解透彻,更加难得可贵的是它是一本英文原著,却有着丰富的难词和背景信息注释。本书出版之后,两位作者都参与起草了敏捷运动的纲领性文件《敏捷宣言》,所以敏捷软件开发可以......