React学习6(React hook)

11 篇文章 0 订阅

React Hooks 学习笔记

class组件带来的问题:

经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

使用hook的意义

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

什么是hook

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。

Hook 不能在 class 组件中使用 —— 这使得不使用 class 也能使用 React。

react组件类型有两种,一种是class组件(类组件),,另一种是function组件(函数组件),react 之前主流开发是使用class 组件来完成复杂的业务逻辑,因为class组件有state 可以管理数据,,可以通过setState来只渲染state中发生改变的数据,以及各种生命周期函数也可以控制不同时间要执行的功能, 比函数组件更有优势。但是class组件比较大,更加占用性能。因此现在更倾向于使用函数组件来实现业务,而hooks就是弥补函数组件的状态管理的一种技术解决方式。他可以让函数组件也可以拥有自己的状态state。以下是 hooks的需要学习的知识点。

1 useState

useState 的功能类似 setState,通过useState可以声明一个变量和修改变量的方法,
当调用修改变量的方法时,会重新渲染组件

import React,{useState} from 'react';  //首先需要引入useState
const demo = ()=>{
    const [count, setCount] = useState(0); //count就可以理解为放在state的变量,setCount就可以理解为 setState方法,只不过这里的setCount 方法只能用来修改count这一个变量。就像是将每个对象和修改对象的方法单独分开了。 后面useState中的0 是初始值,设置为count初始值为0;
}

值更新与函数更新
setState函数中上面传入的是值来更新的数据,还可以传入一个函数 ,通过函数return的值来更新数据.

import React, { useState } from "react";

const UseStateDemo = () => {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);

  const changeData = () => {
    for (let i = 0; i < 10; i++) {
      setA(a + 1);
      setB((pre) => {
        return pre + 1;
      });
    }
  };

  return (
    <div>
      <h5>A: {a}</h5>
      <h5>B: {b}</h5>
      <button onClick={changeData}>change</button>
    </div>
  );
};

点击change之后的效果
在这里插入图片描述

当change触发是,使用值更新,会做批量处理,所以每次执行setA(a+1)时,a的值一直是0,所以 相当于把setA(1)批量执行了10次, setB使用函数更新,每次都会拿到更新之前的值来进行+1处理,所以就会一直进行+1操作 .
这两者的区别在于 函数更新可以拿到之前的值,如果更新的值依赖于之前的值,那就需要使用函数更新,如果是单纯的更新为一个固定的值,那就可以直接传入一个值.

2 useEffect

useEffect 就像是来替代 类组件的各种生命周期方法,需要传两个参数,第一个是组件渲染时执行的方法,第二个参数是数组,是设置会触发 useEffect方法的变量 ,可以不传,不传的话默认每次组件渲染都触发 ,不传时可以替代 componentDidUpdate的作用,当传值为空数组是只会在第一次渲染时触发,就类似componentDidMount的作用。第一个参数执行的方法中return 一个方法,是解绑函数,会在组件移除之后触发,类似 componentWillMount的作用

import React,{useState,useEffect} from 'react';
const demo = ()=>{
	useEffect(()=>{
        console.log("我来了");

        return ()=>{// 解绑函数
            console.log("我走了********")
        }
    },[])   // inputs 设置会触发 useEffect方法的变量  可以不传,默认每次组件渲染都触发
}

3. 自定义hook

通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。
自定义一个hook,来获取屏幕的页面的宽高。

import React, { useCallback, useEffect, useState } from "react";
// 自定义hook 来获取宽和高
const useResize = () => {
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight,
  });

  const onResize = () => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
    });
  };

  useEffect(() => {
    window.addEventListener("resize", onResize);
    return () => {
      window.removeEventListener("resize", onResize);
    };
  });

  return size;
};

const FuncComponent = () => {
  const { width, height } = useResize();
  return (
    <div>
      <h3>width: {width}</h3>
      <h3>height: {height}</h3>
    </div>
  );
};

export default FuncComponent;

自定义的hook 来封装可复用的逻辑,相比于封装普通函数,自定义hook中有可以使用hook, 拥有自己的状态,也可以useEffect等,可以监听各种变化,去对应的更新数据.

4. useMemo与useCallback

useMemo主要用来解决使用React hooks产生的无用渲染的性能问题,函数型组件没有shouldCompnentUpdate(组件更新前触发),我们就没有办法通过组件前的条件来决定组件是否更新.
且在函数组件中,也不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。useMemo和useCallback都是解决上述性能问题的, 以下为练习Demo,在下面Demo中,使用 useCallback与useMemo效果相同,都避免了无用渲染。只不过 useMemo是缓存对象,useCallback是缓存方法.

// 父组件
import React, { useState, useMemo } from "react";
const UseMemoDemo = () => {
  const [hong, setHong] = useState("小红工作中");
  const [lan, setLan] = useState("小兰工作中");
  return (
    <div>
      <h2>这里是父组件</h2>
      <button
        onClick={() => {
          setHong("小红" + new Date().toLocaleTimeString());
        }}
      >
        小红
      </button>
      <button
        onClick={() => {
          setLan("小兰" + new Date().toLocaleTimeString());
        }}
      >
        小兰
      </button>
      <Children hong={hong} lan={lan}></Children>
    </div>
  );
};
export default UseMemoDemo;

// 子组件
const Children = ({ hong, lan }) => {
  function changeHong() {
    console.log(hong + "  正在工作...");
    return hong;
  }

  const xiaohong = useMemo(() => changeHong(), [hong]);
  return (
    <>
      <div>{hong}</div>
      <div>{lan}</div>
    </>
  );
};

useCallback Demo

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

const Child = (props) => {
  console.log("render");

  const logA = () => {
    console.log(props.a);
  };

  const memoizedCallback = useCallback(() => {
    logA();
  }, [props.a]);

  return (
    <div>
      111 <button onClick={logA}>打印</button>
    </div>
  );
};

const UseCallbackDemo = () => {
  const [data, setData] = useState({ a: "a", b: "b" });
  const { a, b } = data;
  return (
    <div>
      <Child a={a} b={b} />
      <button
        onClick={() => {
          setData({ ...data, a: a + new Date() });
        }}
      >
        change A
      </button>
      <button
        onClick={() => {
          setData({ ...data, b: b + new Date() });
        }}
      >
        change B
      </button>
    </div>
  );
};

export default UseCallbackDemo;

5.useContext 来解决父子组件传值的问题

其实父子组件传值最简单的方式应该是 通过 props,但是有可能是父组件给 子组件的子组件传值,这样再通过props 可能就相对麻烦了,所以有了 上下文的用法

useContext,上下文的使用 解决了 父组件给下面几层的子组件传值繁琐的问题。每一个后代组件都可以简单拿到值。

CountContext.js

import {createContext} from "react";

const CountContext = createContext();  // 创建了一个上下文对象
export default CountContext;

index.jsx

import React,{useState, useEffect, createContext, useContext} from 'react';
import CountContext from "./CountContext"
import Children from "./Children"
const Index = props => {
    const [count, setCount] = useState(0);
    return (
        <div>
            当前Count:+{count}
            <CountContext.Provider value={count} >   
                <Children/>
            </CountContext.Provider>
        </div>
    );
};

Children.jsx

import React,{useState, useEffect, useContext} from 'react';
import CountContext from "./CountContext"
const Children = props => {
    const count = useContext(CountContext); // 这里接收上下文对象传的值
    return <h2>这里展示父组件的上下文值{count}</h2>
}

6. useReducer

useReducer 与useState 有点类似,也是会获取一个对象state和一个改变对象的方法reducer,但是 他的功能要比useState 更多,setReducer传递两个参数,第一个参数是传一个函数,用来控制改变state的逻辑,函数里有两个参数,第一个是之前的state,第二个是action,是调用useReducer返回的函数传递的参数,第二个参数是传state初始值

例如

import React, { useReducer } from 'react';
const reducer = (prevState, action)=>{
    switch (action) {
        case "add":
            return prevState+1;
        case "sub":
            return prevState-1;
        default:
            return prevState;
    }
}

const UseReducerDemo = ()=> {
    const [count,dispatch] = useReducer(reducer,0);  //0 就是设置的初始值,可以不传  reducer就是要传递的改变state的逻辑函数,内置了两个参数,第一个就是之前的state值,第二个就是调用dispatch传递进去的参数,
    return (
        <div>
            <h2>count:{count}</h2>
            <button onClick={()=>dispatch("add")}>add</button>
            <button onClick={()=>dispatch("sub")}>sub</button>
        </div>
    );
};
export default UseReducerDemo;
7. useContext 与 useReducer 结合 redux的状态管理和状态共享

实现状态全局化并能统一管理,统一个事件的派发

案例:点击按钮切换对应的字体颜色

// 父组件
import React ,{createContext} from 'react';
import {Colors} from "./colors";  
import ShowArea from "./showArea";
import Buttons from "./buttons";

const AreaControl = () => {
    return (
        <div>
            <Colors>
                <ShowArea/>
                <Buttons/>
            </Colors>
        </div>
    );
};
export default AreaControl;


// 子组件,接收父组件传递的颜色并设置字体颜色
import React ,{useContext} from 'react';
import {ColorContext} from "./colors";

const ShowArea = () => {
    const {color} = useContext(ColorContext);  // 接收全局的颜色值
    return (
        <div style={{color: color}}>
            这是颜色区域
        </div>
    );
};
export default ShowArea;

// 控制颜色按钮组件   用来向全局派发事件来控制全局的颜色值
import React,{useContext} from 'react';
import {ColorContext, type} from "./colors";

const Buttons = () => {
    const {dispatch } = useContext(ColorContext);  // 从全局传递的参数中拿到修改参数的方法,进行事件派发
    return (
        <div>
            <button onClick={()=>{dispatch({type: type, color: "green"})}}>绿色</button>
            <button onClick={()=>{dispatch({type: type, color: "red"})}}>红色</button>
        </div>
    );
};
export default Buttons;

//全局状态管理组件,创建全局上下文对象,在向子组件传递 全局参数和派发事件的方法
import React ,{createContext, useContext, useReducer } from 'react';

export const ColorContext = createContext(); // 创建全局上下文对象
export const type = "Change_Color";

// 编写修改state状态值的逻辑方法
const reducer = (state, action)=>{
    switch (action.type) {
        case type:
            return action.color;
        default:
            return state;
    }
}
export const Colors = props => {
    const [color, dispatch] = useReducer(reducer,"blue");// 获取state值和修改state的事件, 设置state默认值为blue

    return (
        <ColorContext.Provider value={{color, dispatch}}> <!--将state和修改state的事件传递下去,子组件可以通过useContext 拿到-->
            {props.children}  <!--展示嵌套的子组件-->
        </ColorContext.Provider>
    )
}

8.useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

一个常见的用例便是命令式地访问子组件:

import React, {useRef} from 'react';

const UseRefDemo = () => {
   const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
};

export default UseRefDemo;

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以

形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: …} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

本文是学习b站技术胖的react hooks(https://www.bilibili.com/video/BV1y4411Q7yH) 做的笔记,以及参考了下面这篇文章,如有侵权,请联系本人删除https://blog.csdn.net/pz1021/article/details/104763207

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值