函数组件就是类组件的render,只要重新渲染整个函数都会从上到下都会渲染 ,class 只是render 函数执行
Useupdate
import { useState } from "react";
useState是一个函数 接受一个参数作为初始值 返回一个数组 数组的第一项是state 第二项是修改state的方法
例如:
import React, { useState } from "react";
export default function UseStateDemo() {
let [num, setNum] = useState(10);
console.log("render");
return (
<div>
<h1>函数组件{num}</h1>
<button
onClick={() => {
// let count = ++num
// console.log(count)
// setNum(count)
// setNum((val) => {
// console.log('xxx',val)
// return 56
// })
setNum((prev) => prev + 1);
}}
>
++
</button>
</div>
);
}
UseEffect
第一个参数 更新的时候触发的回调方法
第二个参数 是一个数组 存放依赖项, 只有当依赖的数据发生改变的时候才会触发回调
第一个参数里return一个函数 该函数在组件卸载的时候触发
因为函数组件里面没有生命周期,所以可以用来模拟类组件里面的
componentDidMount
componentDidUpdate
componentWillunMount
componetWillReceiveProps
例子:
import React, { useEffect, useState } from "react";
import Son1 from "./Son1";
import Son2 from "./Son2";
export default function Demo() {
const [count, setCount] = useState(0);
const [num, setNum] = useState(0);
const [show, setShow] = useState(true);
//模拟componentDidMount
useEffect(() => {
console.log("componentDidMount");
}, []); //这个地方模拟了componentDidMount,每当更新的时候都会触发,后面的数组,如果你写[num],他就只会监听num的变化,写哪个变量就会监听哪一个变量的,要是都不写,所有的变量都会触发
const toggle = () => {
setShow(show => !show) //这里是为了模拟组件son1的卸载生命周期
}
return (
<div>
<h1>
useEffect{count} | {num}
</h1>
{show&&<Son1></Son1>}
<button onClick={toggle}>toggle</button>
<button
onClick={() => {
setCount((val) => val + 1);
}}
>
count++
</button>
<button
onClick={() => {
setNum((val) => val + 1);
}}
>
num++
</button>
<hr />
<Son2></Son2>
</div>
);
}
son1.js
import React,{useEffect} from "react";
function Son1() {
useEffect(() => { //这里是模拟的函数卸载的生命周期,当这个组件不显示的时候就会打印组件卸载
return() => {
console.log("组件卸载")
}
},[])
return (
<div>
这里是子组件
</div>
);
}
export default Son1
son2.js
import React, { useEffect, useRef, useState } from "react";
function Son2() {
const [count, setCount] = useState(11);
const ref = useRef(true)
console.log("ref", ref);
useEffect(() => { //这里模拟了函数组件内部count参数的变化,首先给son2组件绑定了一个全局的ref为true,所以第一次进来的时候,并不执行更新阶段的生命周期,只是执行挂载生命周期,只行为ref变成false,以后再更新就只会执行更新的生命周期了,不在执行挂载
if (ref.current) {
console.log("第一次进来 didmount");
ref.current = false;
} else {
console.log("componentDidUpdate");
}
});
const add = () => {
setCount((value) => value + 3);
};
return (
<div>
这里是子组件{count}
<button onClick={add}>chagne</button>
</div>
);
}
export default Son2;
useLayoutEffect
useEffect useLayoutEffect 执行的时机不一样
useEffect 在页面渲染完毕后执行
useLayoutEffect dom渲染结束 页面渲染之前执行
例子:我们用一个动画来实现,要是在挂载之前动画是不会变的,不会展示在页面
import React, { useEffect, useLayoutEffect, useState, useRef } from "react";
function Index() {
const div = useRef();
const move = (px, ms) => {
div.current.style.transform = `translate(${px}px)`;
div.current.style.transition = `all ${ms}ms ease-in`;
};
/* useEffect(() => { //当挂载的时候才会执行动画,动画会显示出来
console.log("useEffect")
move(400,500)
},[]) */
useLayoutEffect(() => { //在挂载之前div就在400px了。所以当挂载的时候显示在我们页面上就是400px了
console.log("useLayoutEffect")
move(400,500)
})
return (
<div>
<h1>useLayoutEffectDemo</h1>
<div
ref={div}
style={{ width: "100px", height: "100px", background: "red" }}
>
测试div
</div>
</div>
);
}
export default Index;
useContext
是用来用来简化context上下文传参的,在组件内部不在用Consumer,直接通过useContext获取就行
例如先创建两个上下文的文件用来存放数据
colorContext.js
import { createContext } from "react";
export default createContext();
sizeContext.js
import { createContext } from "react";
export default createContext();
在index.js
通过Provider进行在组价里面传递值
import React,{useState} from "react";
import colorContext from "./colorContext";
import sizeContext from "./sizeContext";
import Son1 from "./Son1";
import Son2 from "./Son2"
// 通过provider 提供的上下文
function Index() {
const [color,setColor] = useState('red')
const changeColor = (color) => {
setColor(color)
}
return (
<colorContext.Provider value={{color, changeColor}}>
<sizeContext.Provider value={{size:44}}>
<div>
<Son1></Son1>
</div>
</sizeContext.Provider>
</colorContext.Provider>
);
}
export default Index;
在son1.js里面
区别是之前我们通过Consumer进行获取参数,现在我们通过useContext
import React, { useContext } from "react";
import colorContext from "./colorContext";
import sizeContext from "./sizeContext";
function Son1() {
const color = useContext(colorContext);//在这里我们能直接获取index里面通过provider传的值
const size = useContext(sizeContext);
return (
<div>
<p style={{ color: color.color, fontSize: size.size + "px" }}>三个金</p>//这里面能直接用获取的上下文的值
<button
onClick={() => {
color.changeColor("green");
}}
>
change
</button>
</div>
);
}
export default Son1;
useReducer
是用来进行store里面传参,执行函数的,跟类组件里面不同
在reducer.js里面
export const defaultState = { //默认值
name: "韩梅梅"
}
export const reducer = (state,action) => {
let newData = {...state};
const {type,payload} =action;
switch (type) {
case "changeName":
newData.name = payload
break;
default:
break;
}
return newData
}
在index.js里面
import React, {useReducer} from "react";
import { defaultState, reducer } from "./reducer"
function Index() {
const [state,dispatch] = useReducer(reducer,defaultState)
// console.log(result)
return (
<div>
<h1>useReducerdemo{state.name}</h1>
<button onClick={() => {
dispatch({
type: 'changeName',
payload: "隔壁老王"
})
}}>change</button>
</div>
);
}
export default Index
useCallback
作用是减少父组件改变后子组件的渲染次数
首先来看在子组件中
思路:
1. 父组件render 子组件一定render
2. memo purecomponent shoucompoentUpate 可以优化
3. 向子组件传递数据的时候 使用箭头函数 每次render都响当于
创建一次新的箭头函数,哪怕处理逻辑都一样 子组件会render
4. 将函数挂载到this上 可以减少子组件的render
例子:
在index.js父组件里面
import React, { Component } from "react";
import Son1 from "./Son1";
class Demo extends Component {
state = {
num: 1,
sum:3
};
add = () => {
this.setState({ num: 2 });
}
render() {
return (
<div>
app组件{this.state.num}
//下面就是直接把方法或者属性挂载到this上传递给子组件,这样只用当传递给子组件的方法或者属性发生改变的时候才会触发子组件的渲染,父组件其他只发生改变子组件不会发生改变
<Son1 sum={this.state.sum}
add={this.add}
></Son1>
//这是通过箭头函数的方法传递给子组件,在父元素中不管什么值发生改变,子组件都会发生渲染
{/* <Son1
add={() => {
this.add()
}}
></Son1> */}
<button onClick={() => {
this.setState({sum:2})
}}>add</button>
</div>
);
}
componentDidMount() {}
}
export default Demo;
在son.js里面
import React, {memo} from "react";
function Son1(props) {
console.log("render")
return (
<div>
组件1{props.sum}
<button onClick={() => {
props.add(5)
}}>add1</button>
</div>
);
}
// memo等同于purecomponent
// 组件的渲染优化也是浅拷贝层级
export default memo(Son1)
而在函数组件中没有this,只有通过useCallback实现
思路:
在函数组件中没有this帮助我们挂载方法,导致子组件的无效渲染
通过useCallBack 可以实现保存函数组件的方法 也被称之为”记忆函数“
第二个参数也是依赖项 依赖的数据发生改变也会发生改变
在index.js里面
import React, { useState, useCallback } from "react";
import Son1 from "./Son1";
function Index() {
const [num, setNum] = useState(0);
const [color, setColor] = useState("red");
// const add = () => { 这种方法不管父元素任何属性发生变化,子元素都会重新渲染
// setNum(num => num +1)
// }
const add = useCallback(() => {
setNum((num) => num + 1);
},[]); //这里通过useCallback,把这个方法传递到子组件里面,只有在[]参数里写上某个属性,只有父元素的这个属性发生改变的时候子组件才会渲染,不传的除了其他传给子组件的属性改了,子组件发生渲染,父组件其他属性发生改变子组件不会渲染,其他传给子元素的属性像下面还给子元素传了一个num
return (
<div>
app组件{num}
<p style={{color}}>color</p>
<button onClick={() => {
setColor("blue")
}}>changecolor</button>
<Son1 add={add} num={num}></Son1>
</div>
);
}
export default Index;
son.js
import React, {memo} from "react";
function Son1(props) {
console.log("render")
return (
<div>
组件1{props.num}
<button onClick={() => {
props.add(5)
}}>add1</button>
</div>
);
}
export default memo(Son1)
useMemo
记忆组件 computed
方式一
下面这个方法是nodemon里面的需要npm i memoize-one
import memoizeOne from 'memoize-one';
import React, { Component } from "react";
import memoizeOne from 'memoize-one';
class Demo extends Component {
state = {}
add = (a,b) => {
console.log("add执行")
return a + b
}
memoAdd = memoizeOne(this.add)//通过这个方法当执行的函数一样的时候,默认函数只会执行一次,要是不用这个方法,直接this.add(1,2)有几次就会执行几次
render() {
return (
<div>
app组件
</div>);
}
componentDidMount() {
// this.add(1,2)
// this.add(1,2)//执行两次
this.memoAdd(1,2)
this.memoAdd(1,2)
this.memoAdd(1,2)
this.memoAdd(1,2)
this.memoAdd(1,2) //上面的参数函数都是一样的,所以只会执行一次这个函数
}
}
export default Demo;
方法2,通过hook里面的方法useMemo
import React, {useState, useMemo} from "react";
function Index() {
const [num, setNum] = useState(0)
const [count, setCount] = useState(0)
const dbNum = useMemo(() => { //跟Vue里面计算属性一样,只有num发生改变的时候才会触发,因为第二个参数数组里面写的num,如果不写的话什么都见听不到,如果return的是num,而数组里面写的是count,count改变也不会触发
console.log("计算属性")
return num * 2
},[num])
return (
<div>
<h1>useMemo{num} | {count}</h1>
<p>dbNum{dbNum}</p>
<button onClick={() => {
setNum(num => num + 1)
}}>Numchange</button>
<hr />
<button onClick={() => {
setCount(count => count + 1)
}}>countChange</button>
</div>
);
}
useRef
用于取到函数组件里面绑定的ref,注意函数组件是没办法直接拿到整个函数组件的,只能拿到函数组件内部的dom,
下面是检测能不能拿到函数组件与函数组件里面的dom
index.js
import React, {useState,useEffect, useRef} from "react";
import Son1Class from "./Son1Class"
import Son2Fun from "./Son2Fun"
function Index() {
const [num, SetNum] = useState(0)
const p = useRef();
const son1 = useRef()
const son2 = useRef()
useEffect(() => {
console.log(p.current.innerHTML)//10,所以可以通过这个方法拿到函数组件内部的dom
console.log(son1.current) //son1是类组件,可以拿到
console.log(son2.current)//son2是函数组件,拿不到是空,要想拿到,需要在组件内部通过useImperativeHandle把绑定ref的dom给return出来
},[])
return (
<div>
<p ref={p}> {num}</p>
{/* 类组件使用ref */}
<Son1Class ref={son1}></Son1Class>
{/* 函数组件使用ref */}
<Son2Fun ref={son2}></Son2Fun>
</div>
);
}
export default Index
/*
在函数组件里面要用到一个插件,把函数组件导出forwardRef
作用:因为父元素要拿到子元素的dom,而不是拿子组件,所以作用是当其作用于子组件时,因为中间隔了一层,ref 就会传递失败,最终导致父组件调用ref.current.xxx()时失败
son2Fun.js
import React,{ forwardRef } from "react";
import React,{ forwardRef } from "react";
function Index(props, ref) {
console.log(ref)
return (
<div>
<p ref={ref}>子组件的p元素</p>
函数组件
</div>
);
}
export default forwardRef(Index)
改造:父组件就可以拿到绑定函数组件p的ref的dom了
import React,{ forwardRef,useImperativeHandle,useRef,useEffect } from "react";
function Index(props, ref) {
const pref=useRef()
useEffect(()=>{
console.log(pref.current);
},[])
useImperativeHandle(ref, () => { //通过useImperativeHandle把孩子函数组件内部绑定的ref给return出去,在父组件就拿到函数子组件里面绑定的所有ref
return {
pref
}
})
return (
<div>
<p ref={pref}>子组件的p元素</p>
函数组件
</div>
);
}
export default forwardRef(Index)
通过useRef创建一个在当前组件不会随着函数重新执行而覆盖的实例
函数组件缓存数据
函数组件每一次 render ,函数上下文会重新执行,那么有一种情况就是,在执行一些事件方法改变数据或者保存新数据的时候,有没有必要更新视图,有没有必要把数据放到 state 中。这时候更新无疑是一种性能上的浪费。
useRef 可以创建出一个 ref 原始对象,只要组件没有销毁,ref 对象就一直存在,那么完全可以把一些不依赖于视图更新的数据储存到 ref 对象中。这样做的好处有两个:
第一个能够直接修改数据,不会造成函数组件冗余的更新作用。
第二个 useRef 保存数据,如果有 useEffect ,useMemo 引用 ref 对象中的数据,无须将 ref 对象添加成 dep 依赖项,因为 useRef 始终指向一个内存空间,所以这样一点好处是可以随时访问到变化后的值。
例子
index.js
function Index() {
console.log(10000)
const [num, SetNum] = useState(0)
//const x = useRef(100)
let x=100 //如果你只是单纯的定义一个x=100,在下面函数x++你每次执行++这个函数,这个函数组件就会重新执行,x又将会变成100,每次点击++都是101,但是你要是通过上面的useRef(100)把他存起来,每次执行让useRef++,就每次都会加1
return (
<div>
<p> {num}</p>
<button onClick={() => {
// console.log(x.current) //100,101,102 .....
console.log(x);//每次都是100
// x.current ++
x++
SetNum(num => ++num)
console.log(x); //每次都是101
// console.log(x.current)//101 102 103......
}}>change</button>
</div>
);
}
export default Index
useImperativeHandle
将父组件的ref 和子组件的ref进行透传关联,在上面useRef中我们知道,不能直接拿到函数组件,只能拿到函数组件的dom,但是父组件怎么拿到子函数组件绑定dom的ref呢,就是通过这个方法,
同样只要是在函数组建中需要拿到他的ref就需要forwardRef,将这个函数组件导出
在孩子函数组件son1.js里面
mport React, {forwardRef, useImperativeHandle, useRef} from "react";
function Son1(props, ref) {
const inputRef = useRef();
const divRef = useRef();
useImperativeHandle(ref, () => { //通过useImperativeHandle把孩子函数组件内部绑定的ref给return出去,在父组件就拿到函数子组件里面绑定的所有ref
return {
inputRef,
divRef
}
})
return (
<div>
<input ref={inputRef}type="text" />
<div ref={divRef}></div>
</div>
);
}
export default forwardRef(Son1)
在index父组件进行接收
index.js
import React, {useEffect, useRef} from "react";
import Son1 from "./Son1"
function Index() {
const input = useRef();
useEffect(() => {
input.current.inputRef.current.focus() //给子组件输入框绑定焦点,进入页面就有焦点
console.log(input)//是子组件里面所有dom的ref
},[])
return (
<div>
<h1>useImperativeHandleDemo</h1>
<Son1 ref={input}></Son1> //这个地方能拿到函数子组件绑定的所有ref
</div>
);
}
export default Index
自定义hooks
就相当于之前的Utils,把方法,跟属性放一个文件夹里面,当很多地方都要用这个方法的时候,直接把这个文件夹引入,用里面的方法就行
例1实现一个更新的生命周期
useUpdateHook.js
import { useEffect, useRef } from "react";
const useUpdateHook = (cb) => {
const ref = useRef(true);
useEffect(() => {
if (ref.current) {
ref.current = false;
} else {
cb();
}
});
};
export default useUpdateHook;
在index.js里面引入这个方法进行使用
import React, {useState, useCallback } from "react";
import useUpdateHook from "./useUpdateHook";
function Index() {
const [count, setCount] = useState(1);
useUpdateHook(() => {
console.log("componetDidUpdate")
})
const add = useCallback((count) => {
setCount((num) => num + count);
});
return (
<div>
<h1>自定义hook</h1>
{count}
<button onClick={add.bind(null, 3)}>add</button>
</div>
);
}
export default Index;