React笔记

1、初识

  • 类组件class

  • 使用ES6面向对象的语法,有生命周期函数、this、state、ref、context

  • 缺点:性能比函数式组件差一点,已经不是主流,但仍需掌握

  • 函数组件

  • 自React就有,没有...,配合hook达到类组件的功能

import { Component } from 'react'

class DemoA extends Component {
    render(){
        return (<h1>类组件class</h1>)
    }
}

function DemoB() {
    return (<h1>函数式组件</h1>)
}

export default DemoB

2、jsx

  • 给组件提供视图模板,是可选的

  • 写法

  • jsx

  • React.createElement:浏览器不能识别jsx ,使用babel翻译成React.createElement的形式

  • 本质:React.createElement()的返回值,即一个不可变对象 ---- 因为jsx元素最终要被渲染成真实的DOM元素,所以不能对这个对象中的属性直接进行操作,只能使用

  • JSX对象可以叫做”fiber单元",很多很多的嵌套的”fiber单元"就会构成”fiber树"

  • 嵌套表达式必须用 {} 包起来 任何有值的内容都是表达式

  • 可以做函数入参、返回值,还可用在if、for中

  • 支持点语法

  • 三个变化的属性

  • class ===> className

  • for ===> htmlFor

  • tabindex ===> tabIndex

  • 新增三属性

  • key --- 列表渲染

  • ref --- 便于dom操作

  • dangerouslySetInnerHTML --- 渲染一片HTML字符串

  • 只能有一个根标签, 根标签还可以这样写: <> </>

  • 组件和任何HTML标签都可以使用单标签

  • 双标签之间内容是通过props.children来接收,可以是任何类型的数据

  • 所有组件名必须以大写字母开头!如果小写,就是普通函数

  • 行内样式必须是对象形式,而对象是js代码,js代码需要写在 {} 中

  • 不管是类组件,还是函数式组件,都有props(父给子传递数据)

  • 对组件来说,props---自定义属性,props只能使用,不能修改;对HTML标签来说,props---标签属性

  • 在JSX中是不能直接渲染一个对象,但是可以渲染一个数组,而如果数组中有Boolean值、null、undefined,会直接被忽略,不会生成对应的文本节点

  • 使用ref可以获取DOM元素

3、状态

类组件

必须有render

  • constructor中 this.state={}定义状态

  • this.state.xxx 直接修改状态,但render不会重新执行,不推荐

  • this.setState 修改状态,有两种写法:

  • this.setState({}, callback)

新值与旧值无关时,推荐使用这种写法,callback表示当状态修改后,自动执行,当状态修完后,有一些业务逻辑放到callback中。

  • this.setState((state, props)=>({}), callback)

新值与旧值有关时,新值由旧值计算而来,形参state永远表示旧值,建议使用这种写法。callback同上。

  • 为了性能优化,在V18中,this.setState设计成异步,这种特性叫”并发模式“

在同一个函数作用域中,为了减少没必要的diff运算,多次this.setState会自动合并成一次,叫协调运算

  • 修改状态的同步异步问题

  • react18中:异步 (并发模式)

  • react18前:合成事件 --- 异步, 宏任务&Promise.then --- 同步

  • 避免至二级修改(只使用老状态,不修改老状态)

函数组件
  • 需要配合hook(useState)定义状态

  • 参数1---状态 参数2---修改状态

  • 形参变量名可以使用 _ 代替 ,表示老状态

 let [num, setNum] = useState(1)
 let add = () => {
     // 写法1
     // setNum(num + 1)
     // 写法2
     setNum(_=>_+1)
  }
  • setNum(修改状态)中,没有callback

  • 同步异步问题

  • V18之前:合成事件---异步,宏任务&promise.then---同步

  • V18:异步

初识事件绑定

  • onXxx----合成事件

 <button onClick={this.add}>+1</button>

上面的写法不完美,因为在监听器中不能获取到this,是und。

  • 解决方法:

  • 利用ES5中的bind中手动绑定this

 <button onClick={this.add.bind(this)}>+1</button>
  • 利用ES6中的箭头函数自动绑定this,推荐

 <button onClick={()=>this.add()}>+1</button>
  • 在constructor中进行this绑定

constructor(props){
    super(props)
    this.state={
        num:1
    }
    this.add=this.add.bind(this)
}

4、条件渲染

在React中,没有指令,使用原生JS来实现

import { Component } from "react";

class DemoA extends Component {
    constructor(props) {
        super(props)
        this.state = {
            flag: true,
            num: 0
        }
    }
    
    // 多个元素---封装自定义渲染函数
    renderHn() {
        let { num } = this.state
        let res = null
        if (num === 1) res = <h1>im h1</h1>
        if (num === 2) res = <h2>im h2</h2>
        if (num === 3) res = <h3>im h3</h3>
        return res
    }
    
    render() {
        let { flag } = this.state
        return (
            <div>
                <h1 style={{ display: (flag ? 'block' : 'none') }}>类组件</h1>
                {/* 单一元素 */}
                {flag && <h1>h1</h1>}
                
                {/* 多个标签 */}
                {flag &&
                    (
                        <div>
                            <h1>hola</h1>
                            <h1>hola</h1>
                        </div>
                    )}
                
                {/* 两个元素 */}
                {flag ? <h2>h2</h2> : <h3>h3</h3>}
                
                {/* 多个元素 */}
                {this.renderHn()}
                
                {/* 实现显示隐藏 */}
                <button onClick={() => this.setState(_ => ({ flag: !_.flag }))}>show/hidden</button>
                <button onClick={() => this.setState(_ => ({ num: (_.num + 1) % 7 }))}>change num</button>
            </div>
        )
    }
}

export default DemoA

5、表单绑定、列表渲染

  • 受控表单:一个表单的value或checked由state来决定,通过控制state就可以修改表单数据,这样的表单叫受控表单。

  • 受控组件:一个组件的自定义属性由状态来控制,只有这个状态发生变化,组件才能更新。后面讲。

  • 列表渲染:通过map方法实现。因为map方法可以对数据进行加工,返回新的数据(jsx)

6、类组件的生命周期

生命周期函数很多,我们需要掌握6个:

  • 装载阶段(3个):constructor, render, componentDidMount

  • 更新阶段(2个):render,componentDidUpdate

  • 卸载阶段(1个):componentWillUnmount

consructor

  • 调用父类的构造函数

  • super必须在第一行

  • props:接收父组件传递的任何数据,其数据流与state的数据流必须独立

  • 一切和业务相关的代码都不能写在constructor钩子函数中

  • 不能调用setState方法(组件未挂载完毕)

  • 不能调用接口

  • 不能进行DOM操作

  • 不能开定时器...

  • 可以:

  • 定义状态

  • 绑定方法的this

render

  • 类组件必须要有这个钩子函数

  • 用于返回组件的视图结构,jsx

  • 调用render函数,会异步生成棵Fiber树(双向链表结构),然后进行协调运算,类似于Vue中的Diff运算,也就是老Fiber树和新Fiber树对比运算。然后,进入到commmit提交阶段,一次性提交Fiber更新DOM。

  • 不能调用setState

  • 页面调用setState后,会re-render重新渲染

componentDidMount

页面第一次渲染完成

  • 可以:

  • 调用接口

  • 开定时器

  • DOM操作

  • 编写业务逻辑

componentDidUpdate

页面再次渲染成功

  • 模拟vue中的监听器,监听数据是否变化(推荐)

  • 不使用componentDidUpdate也能实现类似vue中的监听器:this.setState({}/fn, callback)

利用callback也可以感知到数据变化,但推荐使用componentDidUpdate。因为多个setState会合并,合并后,callback容易出问题。

  • 可以调用setState,但必须给一个出口(终止条件),否则会陷入死循环,循环到一定次数就会报错。

componentWillUnmount

组件即将销毁

  • 可以:

  • 清缓存

  • 清除定时器

  • 关闭长连接

  • 销毁DOM元素...

shouldComponentUpdate

控制是否更新,返回true正常更新,返回false不更新。

在项目中用的不多,是官方提供的一种性能优化方案。

  • 当执行forceUpdate时,会绕过shouldComponentUpdate方法,一定会进入到更新阶段。

  • 可以使用PureCompoentf替代

  • 只有参与页面渲染的状态变化了才会渲染,可以提升性能,尽可能减少生成Fiber树

为什么要使用这个开关呢?

组件中有很多状态,有些状态会参与到界面刷新,但是还有一些状态是不参与到界面更新,也就是状态变了,不需要更新页面的,此时就体现出开关的重要性了。参与页面更新的状态,状态变化了,在showCompoentUpdate中返回true,正常更新。如果没有参与页面刷新的状态变化了,在shouldCompoentUpdate中返回false,就需要再次调用render。这样,就会少生成一次Fiber树。这个钩子函数是用来性能调优的,可以阻塞掉那些不参与视图渲染的状态更新导致的Fiber生成。

React组件渲染(更新)流程

两个阶段组成

  • render阶段:

目标是生成Fiber树,这个过程是异步的,是可中断,并且不会执行任何副作用。到底中断与否,看的是浏览器主线程的忙不忙。

  • commit阶段:

目的是把协调运算的结果,一次性提交渲染或更新真实DOM。这个过程在V18之前是不可中断的,在V18中是可以人为中断的

7、状态提升

子组件间通过把状态定义到父组件实现通信

在react中没有自定义属性或自定义事件,都是props

8、封装组件

UI组件库,官网:https://ant.design/index-cn/

组合是React组件化的设计模式。也就是研究如何封装一个组件,步骤如下:

  • 第一步:根据UI设计图拆解组件。

  • 第二步:把这个独立的组件单独进行封装。

  • 第三步:利用props把组件串联起来。

在封装组件时,我们需要给组件传递非常多的数据,此时,需要对数据进行校验,需要使用一个第三方包,prop-types

"render props":就是可以参与组件的视图渲染的props,在封装组件时,用的非常多。

9、上下文

组件间的通信,类似于Vue中的proveder/inject。

  • 特点:

  • 在组件树中,上下文中一种单向数据流通信,不能颠倒。

  • 通信是可以跨级的,祖先提供数据,后代消费数据。

  • 这个通过方法,不具有响应式

  • 只有类组件有

  • 上下文的使用场景:

  • 路由

  • 状态管理

  • 在一些组件库,如切换主题,切换组件大小...

  • 国际化

  • 使用上下文的步骤:

  • 创建const ThemeContent = React.createContext()创建上下文

  • 使用Provider提供数据,是给后代提供数据

  • 消费上下文中的数据有2种方案

import React, { PureComponent, useState } from 'react'
const ThemeContext = React.createContext()
const { Provider, Consumer } = ThemeContext

// 方法1
class Child extends PureComponent {
    render(){
        return(
        	<div>
            	<h1>子组件</h1>
            </div>
        )
    }
}
Child.contextType=ThemeContext
// 方法2
class Child extends PureComponent {
	render(){  
        return(
        	<Consumer>
            	{
            		(ctx)=>{
                        return(
        					<div>
            					<h1>子组件:{ctx}</h1>
           					 </div>
        				)
                    }
        		}
        )

function Parent(props) {
    return (
        <div>
            <h3>父组件</h3>
            <Child />
        </div>

    )
}

function PageA() {
    return (
        <Provider value={'100'}>
            <div>
                <h2>page</h2>
                <hr />
                <Parent />
            </div>
        </Provider>
    )
}

export default PageA
React中的组件通信:
  • 状态提升(父传子,子传父),核心靠props

  • 上下文,是祖先与后代之间的通信,父子关系不需要明确

  • props穿透,需要搞清楚父子关系,缺点:会让后代的props变得臃肿

10、hooks

官方提供的API,V16.8新增

在函数式组件中模拟类组件的功能,如state,ref,context,生命周期...

  • useState, useEffect, useLayoutEffect,useContext, userReducer, useRef, useMemo, useCallback..

  • 开源hook: react-use, ahooks...

useState状态

定义状态

const [num,setNum]=useState(1)
为什么使用const

避免直接修改状态,要修改状态,就要使用专属方法(参数2)

为什么是数组解构,不是对象解构

如果是对象解构,useState中返回的对象的键是固定的,如果是数组,可以随便起名

setNum(参数2)的2种写法
  • setNum(num+1)

  • setNum(arg=>arg+1) arg是旧num,没有类似类组件中setState的callback

调用setNum,状态改变,组件的更新流程是什么?

调用setNum时,会触发整个函数组件的执行,生成Fiber树,进一步执行协调运算,最后commit提交更新DOM

如何实现+1

调用setNum,整个函数重新执行,按理说useState也会重新执行,num每次都是1;state定义状态时,定义出来的1储存在react最底层,当setNum执行时,并不会重新setState,而是之前底层定义的1

同步异步问题

V18中,异步

useEffect效果

模拟生命周期:componentDidMount/componentDidUpdate/componentWillUnmount

类似于vue中的watchEffect,vue中的watchEffect会自动依赖依赖,React中的useEffect,需要手动指定依赖

  • 一个函数式组件中,可以写多个useEffect,且彼此互不影响

  • 建议一个useEffect只执行一个效果,不要同时执行多个效果

  • 不要把效果直接暴露在函数体内,一定要用useEffect进行控制

useEffect(()=>{
	fn1()  // 效果 --- 相当于componentDidMount()
	return fn2() //清除效果 --- 相当于componentWillUnmount()
},[依赖数组]) // 相当于componentDidUpdate
工作流程
  • 没有''依赖数组"这个参数时

  • 初始化只执行fn1

  • 当re-render时,先执行fn2,再执行fn1

  • 路由切换时,只执行fn2

  • 有''依赖数组"这个参数,但是一个空数组时

  • 初始化只执行fn1

  • 当re-render时,什么也不执行

  • 路由切换时,只执行fn2

  • 有''依赖数组"这个参数(依赖可以有多个)

  • 初始化只执行fn1

  • 只有当依赖数组中的变量发生变化而导致re-render时,先执行fn2,再执行fn1

  • 路由切换时,只执行fn2

useLayoutEffect布局效果

运行机制和useEffect相同,区别在于useLayoutEffect执行更早

一般项目中很少用,在一些第三方库中用的比较多

在这个hook中,不能进行ref或dom操作。

useMemo备忘录

用于性能优化,缓存一些比较消耗性能的计算,类似vue的计算属性

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

参数2:依赖数组,也有3种写法,和useEffect一样

仅当依赖数组中的状态改变时,useMemo才会重新执行耗能的计算

let total = useMemo(() => {
	return 6 * num
},[num])

useCallback回调

用于缓存函数声明,进行性能优化,可以用useMemo替代

const fn = useCallback(() => {}, [依赖数组])

useContext上下文

提供访问上下文的入口

const ctx=useContext(上下文对象)

useRef参考
ref与ref转化:
  • 类组件中,如果ref写在DOM元素上,目的是为了获取DOM元素,进而操作DOM元素。

  • 如果ref写在类组件标签上,目的是为了获取组件实例, 进而实现组件通信

  • 如果ref写在函数组件标签上,会报错。 需要使用ref转发,转发到了函数式组件中的JSX的DOM标签上, 进而获取函数式组件中的JSX的DOM元素。

useReducer减速机

函数式组件中模块redux的数据流,一个useReducer可以替代多个useState,

即一次性定义多个状态,项目中用的不多

const [state,dispath/foreUpdate/setState]=useReducer(reducer,{
    初始值
})
import { useState, useEffect, useReducer } from "react"

// reducer --- 管理员,要修改状态必须通过reducer
// state --- 状态
// action --- 信号,根据不同的信号,就可以针对性地修改状态
// 管理员根据信号进行状态的修改
// 修改状态的流程:1)对于state进行深copy  2)修改更新state  3)返回修改后的state
const reducer = (state, action) => {
    let newState = JSON.parse(JSON.stringify(state)); // 1)对state进行深copy
    // 根据信号更新state
    switch (action.type) {  // action是一个对象,对象中有一个type,不同的type表示不同的信号
        case "NUM_ADD":
            newState.num += 1
            break;
        case "NUM_SUB":
            newState.num -= 1
            break;
    }
    return newState;
}
// 定义初始值
const initState = {
    num: 1,
    list: ["a", "b"],
    falg: true
}

const A = props => {
    // dispatch 派发一个action,管理员就可以收到action
    const [state, dispatch] = useReducer(reducer, initState);
    return (
        <div>
            <h2 >函数组件</h2>
            <h3>{state.num}</h3>
            <h3>{state.list}</h3>
            <button onClick={() => dispatch({ type: "NUM_ADD" })}>+1</button>
            <button onClick={() => dispatch({ type: "NUM_SUB" })}>-1</button>
        </div>
    )
}
export default A

useId

V18新增,返回一个唯一的标识,在函数式组件的整个运行过程中都是唯一的

let id=useId()
return (
	<div>
		<h1>{id}</id>
	</div>
)

useDeferredValue递延值

和防抖类似。和真正的防抖区别在于,这个hook所延迟的时间是不确定,由浏览器自己决定。

上面发送ajax,对服务器造成很多的压力,需要防抖,有了useDeferredValue这个hook,我们就不需要实现防抖了,如下:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值