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 对象以
然而,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