03、非受控组件与受控组件、高阶函数、prop-types、生命周期、hook

news2024/11/23 0:57:12

总结

一、非受控组件与受控组件

  • 非受控组件

表单项不与state数据相向关联, 需要手动读取表单元素的值

借助于 ref获取真实DOM,在通过value获得输入值,使用原生 DOM 方式来获取表单元素值

非受控组件: 表单项不与 state 数据相向关联, 需要手动读取表单元素的值

  • 受控组件

组件中的表单项根据state状态数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中

也就是实现了页面表单项与 state 数据的双向绑定

实现方式

  1. 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
  2. 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
{/* react中虚拟DOM中的name的值一定是和state属性相关的值 */}
<p>用户名:<input type="text" name='user' value={user} onChange={this.changeForm} /></p>
{/* a是咱们自定义的属性名,后期获取的时候需要使用getAttribute()方法 */}
<p>用户名:<input type="text" a='user' value={user} onChange={this.changeForm} /></p>
{/* 在DOM元素对象身上还可以通过data-a的形式添加,后期获取的时候使用dataset.a */}
<p>用户名:<input type="text" data-a='user' value={user} onChange={this.changeForm} /></p>

changeForm = (e) => {
    this.setState({
    	[e.target.name]: e.target.value
    	[e.target.getAttribute('a')]: e.target.value
        [e.target.dataset.a]: e.target.value
    })
}

二、高阶函数

本身也是一个函数

特点:接收函数类型的参数或者返回一个新函数

function fn(f){
	return ()=>{
	    
	}
}


fn1(()=>{})



const p = new Promise((resolve,reject)=>{});
then(value=>{},reason=>{})
p.catch(reason=>{})
setTimeout(()=>{})  setInterval(()=>{})
let arr = [1,2,3];
arr.foreach(()=>{})
arr.map((item,index)=>{})

函数类型参数:Promise、then、catch、setTimeout、setInterval、forEach、map、filter、every、some等

返回函数:闭包函数、bind

function fun(a, b) {

function fun1() {

​ a++;

​ return a;

}

return fun1;

}

fun(10,20);


<p>用户名:<input type="text"  value={user} onChange={this.saveData('user')} /></p>
       
saveData = (type) => {
    return (e) => {
        this.setState({
            [type]: e.target.value
        })
    }
}


三、React脚手架

1、 脚手架的安装与启动

  • 全局安装
  npm i -g create-react-app
  • 切换目录
  create-react-app 目录名称
  • 进入目录
  cd 目录名称
  • 启动项目
  npm start

2、脚手架目录说明

  • public 脚手架服务的网站根目录(静态资源目录)
    • index.html :webpack打包html的模板文件
  • src 源代码开发的目录
    • index.js 打包的入口文件
  • .gitignore git的配置忽略文件

3、 props 校验 (了解)

允许在创建组件的时候,就指定 props 的类型、格式

作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

实现方式:

  1. 导入 prop-types 包 (脚手架自带,无需额外安装)
  2. 使用propTypes来给组件的props添加校验规则

4、 props 设置类型和设置默认值

作用:给 props 设置默认值,在未传入 props 时生效

在class类中实现方式:

import React, { Component } from 'react'
//1、导包
import types from 'prop-types'
export default class Stu extends Component {
    //2、在类内使用静态属性 propTypes 来约束 
    //注意:这个属性不能修改
    static propTypes = {
        name: types.string.isRequired,
        age: types.number,
        hobby: types.array
    }

    //3、为传入的属性设置默认值
    static defaultProps = {
        name: '李四',
        age: 24
    }

    render() {
        let { name, age, hobby } = this.props;
        return (
            <div>
                <p>姓名:{name}</p>
                <p>年龄:{age}</p>
                <p>爱好:{hobby.join(',')}</p>
            </div>
        )
    }
}

5、函数式组件中使用props校验

App.jsx

import Stu from './Stu'
export default function App() {
    //声明一个对象
    let info = {
        // name: '张三',
        age: 23,
        hobby: ['吃饭', '睡觉', '打豆豆']
    }
    return (
        <div>
            <Stu {...info} />
        </div>
    )
}

Stu.jsx

//1、导包
import PropTypes from 'prop-types'
//2、为Stu设置propTypes属性
Stu.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    hobby: PropTypes.array
}
Stu.defaultProps = {
    name: '王二麻子'
}
export default function Stu(props) {
    let { name, age, hobby } = props;
    return (
        <div>
            <p>姓名:{name}</p>
            <p>年龄:{age}</p>
            <p>爱好:{hobby.join('-')}</p>
        </div>
    )
}

四、React组件的生命周期

组件对象从创建到死亡所经历的特定的阶段

React组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用

我们在定义组件时,在特定的生命周期回调函数中做特定的工作

全称:生命周期函数或者是生命周期钩子,其中主要研究的就是组件生命周期的三个阶段

概念:生命周期函数指在某一时刻组件会自动调用执行的函数

本质就是组件内的一些方法,当然在这里咱们讨论的是类式组件,因为函数式组件中有相应的Hook函数

特点就是能够自动执行

其中包括ComponentDidMount()ComponentDidUpdate()ComponentWillUnmount()

1.1 挂载阶段

流程: constructor ==> render ==> componentDidMount

子父类组件父组件渲染:父构造->父render->子构造->子render->子完成挂载->父完成挂载

父constructor =>父render=>子constructor =>子render=>子componentDidMount===>父componentDidMount

触发: ReactDOM.render(): 渲染组件元素

  • constructor: 创建组件时,最先执行

    一般用于:

      1. 初始化state
      1. 为事件处理程序绑定this

        this.xx = this.xx.bind(this)

  • render: 每次组件渲染都会触发

    • 注意: 不能在render中调用setState()
  • componentDidMount: 组件挂载(完成DOM)渲染后

    注意: 这个生命周期钩子从虚拟DOM转化成真实DOM之后,挂载在root元素之后,只执行这么一次

    除非这个组件被卸载之后,重新挂载,还会在此执行,且还是一次

    一般用于:

      1. 定时器
      2. 发送网络请求 axios
      3. 订阅频道
      1. DOM操作 (只要是组件一上来就要做的事情,都应该写在挂载成功的回调中)

1.2 更新阶段

componentDidUpdate: 组件更新(完成DOM渲染)后

流程: render ==> componentDidUpdate

触发: setState() , 组件接收到新的props

如果是父组件中嵌套子组件,
父组件中的状态被更新了:
父组件render->子组件render->子组件componentDidUpdate->…->父组件componentDidUpdate
子组件中的状态被更新了:那么只有这个子组件中的render->这个子组件中的componentDidUpdate执行

1.3 卸载阶段

componentWillUnmount: 组件卸载(从页面中消失) 执行清理操作

测试:当我们更改组件内容并保存之后,React会将之前的组件卸载并重新挂载一个新的组件

顺序:自己的构造->自己的render->卸载钩子函数->重新挂载

constructor->rende->componentWillUnmount->componentDidMount

父组件内容变化时触发
在这里插入图片描述

子组件内容变化触发:

在这里插入图片描述

例如:

  • 清空定时器
  • 取消订阅

流程: componentWillUnmount

触发: 不再渲染组件

五、Hook

  • Hook 是 React 16.8 之后的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • Hook 也叫钩子,本质就是函数,能让你在函数式组件中使用状态生命周期函数等功能
  • Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了

5.1 useState

用来定义状态数据,可以多次调用, 产生多个状态数据

mport React, { useState } from 'react'

export default function App() {
    // console.log(React.useState);
    /**
     * React.useState()方法返回一个数组,
     * 数组中的第一个元素记录state中的状态数据
     * 数组中的第二个元素是一个方法,用来修改状态数据,类似于setState
     */

    //对象解构赋值            
    //注意:React.useState()方法可以在导入react的时候直接做解构,后面就可以直接使用useState()方法
    let [isLogin, setLogin] = useState(true);

    function changeLogin() {
        setLogin(!isLogin);
    }

    return (
        <div>
            <p>现在的状态:{isLogin ? '已登录' : "未登录"}</p>
            {/* 按钮中直接调用函数,不需要写this */}
            <button onClick={changeLogin}>修改状态</button>
        </div >
    )
}

5.2 useRef

功能和类式组件中的createRef()方法相似,可以帮助我们在函数式组件中方便获取真实的DOM元素对象。

import React, { useRef } from 'react'

export default function App() {
    let ipt = useRef();
    let box = useRef();
    let show = () => {
        let v = ipt.current.value;
        box.current.innerHTML += '用户名:' + v + '<br/>';
    }
    return (
        <div>
            <p>
                用户名:<input type="text" ref={ipt} />&nbsp;
                <button onClick={show}>获取</button>
            </p>
            <hr />
            <div id="result" ref={box} style={{ width: 500, height: 500, border: '1px dashed #ccc' }}></div>
        </div>
    )
}

5.3 useEffect

是一个高阶函数,可以在一个组件中多次使用,相当于componentDidMount(组件挂载完成),

componentDidUpdate(组件更新完成) 和 componentWillUnmount(组件将要卸载之前)的组合

语法:React.useEffect(()=>{},[])

当如果不希望在组件更新时再次执行,只希望在组件挂载时执行,则需要给useEffect方法设置第二个参数为数组

数组参数的含义:设置了哪些状态数据修改之后,才会执行回调,

分为两种情况:

  • 数组的参数如果没有任何值的话,表示什么状态数据都不更新,回调函数不执行
  • 当数组中传入state的状态数据,表示当这个状态数据被更新了的时候,才执行回调函数
import React, { useEffect, useState } from 'react'

export default function App() {
    //声明状态
    let [count, setCount] = useState(0);
    let [sex, setSex] = useState(true);
    //调用
    //第一个参数为函数,箭头函数或匿名函数均可
    //这个函数的作用等效于ComponentDidMount和ComponentDidUpdate
    //这个方法可以执行多次
    useEffect(function () {
        console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-1');
    });

    useEffect(function () {
        console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-2');
    });

    //当如果只想要ComponentDidMount效果,则需要给useEffect方法传入第二个参数,为数组
    //数组的参数如果没有任何元素的话,表示不更新状态

    /* useEffect(() => {
        console.log('我只执行ComponentDidMount效果');
    }, []) */

    //如果想要某一个状态数据改变的时候,需要执行,则将这个状态数据添加即可
    /* useEffect(() => {
        console.log('我只执行ComponentDidMount效果');
    }, [sex]) */

    //如果想要模拟ComponentwillUnmount
    //在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
    useEffect(() => {
        return () => {
            console.log('组件将要被卸载时...')
        }
    })

    let changeAdd = (num) => {
        return () => {
            count = count + num;
            setCount(count);
        }
    }
    let changeSex = () => {
        setSex(!sex);
    }
    return (
        <div>
            <ul>
                <li>
                    <p>数值:{count}</p>
                    <button onClick={changeAdd(10)}>新增</button>
                </li>
            </ul>
            <ul>
                <li>
                    <p>性别:{sex ? '男' : '女'}</p>
                    <button onClick={changeSex}>修改</button>
                </li>
            </ul>
        </div>
    )
}

但是使用useEffect()方法的时候有一个很重要的细节,那就是useEffect方法的第一个函数不能是一个异步函数

也就是说,不能出现async,原因是如果设置了async,返回值就成了promise对象,非一个函数了。

如果想要在useEffect()方法中添加异步代码,需要在函数中在单独声明一个函数

//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
    return () => {
        async function main() {
            console.log('在这里可以执行异步代码逻辑...');
        }
        main();
    }
})

使用Hook 注意:

  1. 只在最顶层使用 Hook,不要在条件或循环中

  2. 只在React组件函数内部中调用 Hook, 不要在组件函数外部调用

React全家桶

一、非受控组件与受控组件

  • 非受控组件

表单项不与state数据相向关联, 需要手动读取表单元素的值

借助于 ref获取真实DOM,在通过value获得输入值,使用原生 DOM 方式来获取表单元素值

非受控组件: 表单项不与 state 数据相向关联, 需要手动读取表单元素的值

编码过程

1、初始化创建 ref 容器并保存到组件对象上

2、将 ref 容器通过 ref 属性交给表单项标签 ,渲染时内部会将对应的真实DOM保存到 ref 容器的 current属性上

3、点击提交按钮时, 通过 ref 容器的 current 属性得到 input DOM 元素 => 就可以读取其 value了

不足:不够自动化/无法进行实时数据校验

先来看一个案例:

在这里插入图片描述

import React, { Component } from 'react'

export default class App extends Component {
    //创建真实DOM容器
    user = React.createRef();
    pass = React.createRef();
    render() {
        return (
            <div>
                <form>
                    <h3>登录页面(非受控组件)</h3>
                    <p>用户名:<input type="text" ref={this.user} /></p>
                    <p>密码:<input type="password" ref={this.pass} /></p>
                    <p><button onClick={this.show}>登录</button></p>
                </form>
            </div>
        )
    }

    show = (e) => {
        //禁止表单提交
        e.preventDefault();
        //获取输入的内容
        let username = this.user.current.value;
        let userpass = this.pass.current.value;
        //输出内容
        console.log(`当前的用户名为${username},密码为${userpass}`);
    }
}
  • 受控组件

组件中的表单项根据state状态数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中

也就是实现了页面表单项与 state 数据的双向绑定

后期使用颇多

实现方式

  1. 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
  2. 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
import React, { Component } from 'react';

class App extends Component {
    state = {
        //创建两个初始状态值
        user: '',
        pass: ''
    }
    render() {
        return (
            <div>
                <form>
                    <h3>登录页面(受控组件)</h3>
                    <p>用户名:<input type="text" value={this.state.user} onChange={this.saveUser} /></p>
                    <p>密码:<input type="password" value={this.state.pass} onChange={this.savePass} /></p>
                    <p><button onClick={this.login}>登录</button></p>
                </form>
            </div>
        );
    }

    login = (e) => {
        e.preventDefault();
        console.log(this.state.user);
        console.log(this.state.pass);
    }

    saveUser = (e) => {

        this.setState({
            user: e.target.value
        })
    }

    savePass = (e) => {
        this.setState({
            pass: e.target.value
        })
    }
}

export default App;

优化1: 使用同一个事件函数处理

问题: 2个input的onChange事件处理函数代码重复

解决: 使用一个事件函数

import React, { Component } from 'react';

class App extends Component {
    state = {
        //创建两个初始状态值
        user: '',
        pass: '',
        phone: ''
    }
    render() {
        const { user, pass, phone } = this.state;
        return (
            <div>
                <form>
                    <h3>登录页面(受控组件)</h3>
                    {/** 注意这里表单元素的name属性可以自定义,但是属性值必须是来自于state中的属性名,否则后期无法更新 */}
                    <p>用户名:<input type="text" name='user' value={user} onChange={this.saveData} /></p>
                    <p>密码:<input type="password" name='pass' value={pass} onChange={this.saveData} /></p>
                    <p>手机号:<input type="text" name='phone' value={phone} onChange={this.saveData} /></p>
                    <p><button onClick={this.login}>登录</button></p>
                </form>
            </div>
        );
    }

    saveData = (e) => {
        this.setState({
            [e.target.name]: e.target.value
        })
    }

    login = (e) => {
        e.preventDefault();
        console.log(this.state.user);
        console.log(this.state.pass);
        console.log(this.state.phone);
    }
}

export default App;
import React, { Component } from 'react'

export default class App extends Component {

    //在实例对象身上修改一个state属性
    state = {
        user: '',
        pass: ''
    }

    render() {
        const { user, pass } = this.state;
        return (
            <div>
                <form>
                    <h3>登录页面(非受控组件)</h3>
                    {/* react中虚拟DOM中的name的值一定是和state属性相关的值 */}
                    <p>用户名:<input type="text" name='user' value={user} onChange={this.changeForm} /></p>
                    {/* a是咱们自定义的属性名,后期获取的时候需要使用getAttribute()方法 */}
                    <p>用户名:<input type="text" a='user' value={user} onChange={this.changeForm} /></p>
                    {/* 在DOM元素对象身上还可以通过data-a的形式添加,后期获取的时候使用dataset.a */}
                    <p>用户名:<input type="text" data-a='user' value={user} onChange={this.changeForm} /></p>
                    <p>密码:<input type="password" data-a='pass' value={pass} onChange={this.changeForm} /></p>
                    <p><button onClick={this.login}>登录</button></p>
                </form>
            </div>
        )
    }

    login = (e) => {
        e.preventDefault();
        console.log(e.target)
        console.log('用户名:', this.state.user)
        console.log('密码:', this.state.pass)
    }

    changeForm = (e) => {
        this.setState({
            // [e.target.getAttribute('a')]: e.target.value
            [e.target.dataset.a]: e.target.value
        })
    }
}

二、高阶函数

本身也是一个函数

特点:接收函数类型的参数或者返回一个新函数

function fn(f){
	return ()=>{
	    
	}
}


fn1(()=>{})



const p = new Promise((resolve,reject)=>{});
then(value=>{},reason=>{})
p.catch(reason=>{})
setTimeout(()=>{})  setInterval(()=>{})
let arr = [1,2,3];
arr.foreach(()=>{})
arr.map((item,index)=>{})

函数类型参数:Promise、then、catch、setTimeout、setInterval、forEach、map、filter、every、some等

返回函数:闭包函数、bind

function fun(a, b) {

function fun1() {

​ a++;

​ return a;

}

return fun1;

}

fun(10,20);

import React, { Component } from 'react';

class App extends Component {
    state = {
        //创建两个初始状态值
        user: '',
        pass: '',
        phone: ''
    }
    render() {
        const { user, pass, phone } = this.state;
        return (
            <div>
                <form>
                    <h3>登录页面(受控组件)</h3>
                    {/** 注意这里表单元素的name属性可以自定义,但是属性值必须是来自于state中的属性名,否则后期无法更新 */}
                    <p>用户名:<input type="text"  value={user} onChange={this.saveData('user')} /></p>
                    <p>密码:<input type="password"  value={pass} onChange={this.saveData('pass')} /></p>
                    <p>手机号:<input type="text"  value={phone} onChange={this.saveData('phone')} /></p>
                    <p><button onClick={this.login}>登录</button></p>
                </form>
            </div>
        );
    }

    saveData = (type) => {
        return (e) => {
            this.setState({
                [type]: e.target.value
            })
        }
    }

    login = (e) => {
        e.preventDefault();
        console.log(this.state.user);
        console.log(this.state.pass);
        console.log(this.state.phone);
    }
}

export default App;

三、React脚手架

3.1 脚手架的介绍

React 脚手架是官方提供的 React 开发工具。

脚手架开发项目的特点:

  1. 更安全
    1. 包含了 eslint 配置,实时提醒代码异常
  2. 更方便
    1. 包含了所有需要的配置(jsx编译、devServer…)
    2. 下载好了 react 的依赖包(react,react-dom,babel…)
    3. 搭建好了目录结构
  3. 更高效
    1. 实时预览代码运行效果
    2. 打包优化处理

3.2 npm安装源的工具包

nrm

安装

npm i nrm -g

查看源列表

nrm ls

修改npm的安装源

nrm use taobao

3.3 脚手架的安装与启动

  • 全局安装

    npm i -g create-react-app
    
  • 切换目录

    create-react-app 目录名称
    
  • 进入目录

    cd 目录名称
    
  • 启动项目

    npm start
    

【注:当下载过程中卡住了,可以ctrl+c停止,并且将下载一部分的文件夹删除并重新运行创建项目命令】

3.4 脚手架目录说明

  • public 脚手架服务的网站根目录(静态资源目录)
    • index.html :webpack打包html的模板文件
  • src 源代码开发的目录
    • index.js 打包的入口文件
  • .gitignore git的配置忽略文件

3.5 props 校验 (了解)

对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据

如果传入的数据格式不对,可能会导致组件内部报错

关键问题:组件的使用者不知道明确的错误原因

允许在创建组件的时候,就指定 props 的类型、格式

作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

实现方式:

  1. 导入 prop-types 包 (脚手架自带,无需额外安装)
  2. 使用propTypes来给组件的props添加校验规则

3.6 props 设置类型和设置默认值

作用:给 props 设置默认值,在未传入 props 时生效

在class类中实现方式:

import React, { Component } from 'react'
//1、导包
import types from 'prop-types'
export default class Stu extends Component {
    //2、在类内使用静态属性 propTypes 来约束 
    //注意:这个属性不能修改
    static propTypes = {
        name: types.string.isRequired,
        age: types.number,
        hobby: types.array
    }

    //3、为传入的属性设置默认值
    static defaultProps = {
        name: '李四',
        age: 24
    }

    render() {
        let { name, age, hobby } = this.props;
        return (
            <div>
                <p>姓名:{name}</p>
                <p>年龄:{age}</p>
                <p>爱好:{hobby.join(',')}</p>
            </div>
        )
    }
}

3.7 函数式组件中使用props校验

App.jsx

import Stu from './Stu'
export default function App() {
    //声明一个对象
    let info = {
        // name: '张三',
        age: 23,
        hobby: ['吃饭', '睡觉', '打豆豆']
    }
    return (
        <div>
            <Stu {...info} />
        </div>
    )
}

Stu.jsx

//1、导包
import PropTypes from 'prop-types'
//2、为Stu设置propTypes属性
Stu.propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number,
    hobby: PropTypes.array
}
Stu.defaultProps = {
    name: '王二麻子'
}
export default function Stu(props) {
    let { name, age, hobby } = props;
    return (
        <div>
            <p>姓名:{name}</p>
            <p>年龄:{age}</p>
            <p>爱好:{hobby.join('-')}</p>
        </div>
    )
}

四、React组件的生命周期

组件对象从创建到死亡所经历的特定的阶段

React组件对象包含一系列钩子函数(生命周期回调函数),在特定的时刻调用

我们在定义组件时,在特定的生命周期回调函数中做特定的工作

全称:生命周期函数或者是生命周期钩子,其中主要研究的就是组件生命周期的三个阶段

概念:生命周期函数指在某一时刻组件会自动调用执行的函数

本质就是组件内的一些方法,当然在这里咱们讨论的是类式组件,因为函数式组件中有相应的Hook函数

特点就是能够自动执行

其中包括ComponentDidMount()ComponentDidUpdate()ComponentWillUnmount()

1.1 挂载阶段

流程: constructor ==> render ==> componentDidMount

子父类组件父组件渲染:父构造->父render->子构造->子render->子完成挂载->父完成挂载

父constructor =>父render=>子constructor =>子render=>子componentDidMount===>父componentDidMount

触发: ReactDOM.render(): 渲染组件元素

  • constructor: 创建组件时,最先执行

    一般用于:

      1. 初始化state
      1. 为事件处理程序绑定this

        this.xx = this.xx.bind(this)

  • render: 每次组件渲染都会触发

    • 注意: 不能在render中调用setState()
  • componentDidMount: 组件挂载(完成DOM)渲染后

    注意: 这个生命周期钩子从虚拟DOM转化成真实DOM之后,挂载在root元素之后,只执行这么一次

    除非这个组件被卸载之后,重新挂载,还会在此执行,且还是一次

    一般用于:

      1. 定时器
      2. 发送网络请求 axios
      3. 订阅频道
      1. DOM操作 (只要是组件一上来就要做的事情,都应该写在挂载成功的回调中)

1.2 更新阶段

componentDidUpdate: 组件更新(完成DOM渲染)后

流程: render ==> componentDidUpdate

触发: setState() , 组件接收到新的props

如果是父组件中嵌套子组件,
父组件中的状态被更新了:
父组件render->子组件render->子组件componentDidUpdate->…->父组件componentDidUpdate

子组件中的状态被更新了:那么只有这个子组件中的render->这个子组件中的componentDidUpdate执行

1.3 卸载阶段

**componentWillUnmount:**组件卸载(从页面中消失) 执行清理操作

测试:当我们更改组件内容并保存之后,React会将之前的组件卸载并重新挂载一个新的组件

顺序自己的构造->自己的render->卸载钩子函数->重新挂载

constructor->rende->componentWillUnmount->componentDidMount

父组件内容变化时触发
在这里插入图片描述

子组件内容变化触发:

在这里插入图片描述

例如:

  • 清空定时器
  • 取消订阅

流程: componentWillUnmount

触发: 不再渲染组件

案例1:电子时钟

Timer.jsx

import React, { Component } from 'react';
import moment from 'moment';
export default class Life extends Component {
    constructor() {
        super();
        console.log('执行构造器初始化组件实例对象属性')
        this.state = {
            timestr: moment().format("YYYY-MM-DD HH:mm:ss")
        }
        this.timer = null;
    }

    componentDidMount() {
        console.log('组件挂载完成后执行....')
        this.timer = setInterval(() => {
            this.setState({
                timestr: moment().format("YYYY-MM-DD HH:mm:ss")
            })
        }, 1000)
    }

    componentWillUnmount() {
        console.log('组件将要被卸载的时候')
        clearInterval(this.timer)
    }

    render() {
        console.log('指定render方法了....');
        let { timestr } = this.state;
        return (
            <div style={{ width: 200, height: 30, border: '1px dashed #ccc', lineHeight: '30px', textAlign: 'center' }}>
                {timestr}
            </div>
        );
    }
}

案例2:消息列表

import React, { Component, createRef } from 'react'

export default class Message extends Component {
    ipt = createRef();
    state = {
        messages: [
            '天王盖地虎',
            '小鸡炖蘑菇',
            '宝塔镇河妖',
            '蘑菇放辣椒'
        ]
    }
    sendMessage = () => {
        let v = this.ipt.current.value;
        this.setState({
            messages: [...this.state.messages, v]
        })
        this.ipt.current.value = ''
    }
    render() {
        let { messages } = this.state;
        return (
            <div>
                <input type="text" ref={this.ipt} />&nbsp;
                <button onClick={this.sendMessage}>发送消息</button>
                <hr />
                <div ref={el => this.box = el} style={{ width: 400, height: 300, border: '1px dashed #ccc', overflow: 'auto' }}>
                    <ul>
                        {messages.map((item, index) => {
                            return <li key={index}>{item}</li>
                        })}

                    </ul>
                </div>
            </div>
        )
    }

    componentDidUpdate() {
        console.log('组件中的状态更新了....');
        this.box.scrollTop = 20000;
    }
}

五、Hook

  • Hook 是 React 16.8 之后的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • Hook 也叫钩子,本质就是函数,能让你在函数式组件中使用状态生命周期函数等功能
  • Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了

5.1 useState

用来定义状态数据,可以多次调用, 产生多个状态数据

mport React, { useState } from 'react'

export default function App() {
    // console.log(React.useState);
    /**
     * React.useState()方法返回一个数组,
     * 数组中的第一个元素记录state中的状态数据
     * 数组中的第二个元素是一个方法,用来修改状态数据,类似于setState
     */

    //对象解构赋值            
    //注意:React.useState()方法可以在导入react的时候直接做解构,后面就可以直接使用useState()方法
    let [isLogin, setLogin] = useState(true);

    function changeLogin() {
        setLogin(!isLogin);
    }

    return (
        <div>
            <p>现在的状态:{isLogin ? '已登录' : "未登录"}</p>
            {/* 按钮中直接调用函数,不需要写this */}
            <button onClick={changeLogin}>修改状态</button>
        </div >
    )
}

案例1:利用按钮增加数值

import React, { useState } from 'react'

export default function App() {
    //由于数组中的解构,所以变量名可以进行自定义
    let [count, setCount] = useState(0);
     let changeCount = (num) => {
        setCount(count + num)
    }
    return (
        <div>
            <p>点击次数:{count}</p>
            <button onClick={e => changeCount(10)}>点击增加十次</button><br /><br />
            <button onClick={changeCount(100)}>点击增加一百次</button>
        </div>
    )
}

5.2 useRef

功能和类式组件中的createRef()方法相似,可以帮助我们在函数式组件中方便获取真实的DOM元素对象。

import React, { useRef } from 'react'

export default function App() {
    let ipt = useRef();
    let box = useRef();
    let show = () => {
        let v = ipt.current.value;
        box.current.innerHTML += '用户名:' + v + '<br/>';
    }
    return (
        <div>
            <p>
                用户名:<input type="text" ref={ipt} />&nbsp;
                <button onClick={show}>获取</button>
            </p>
            <hr />
            <div id="result" ref={box} style={{ width: 500, height: 500, border: '1px dashed #ccc' }}></div>
        </div>
    )
}

5.3 useEffect

是一个高阶函数,可以在一个组件中多次使用,相当于componentDidMount(组件挂载完成),

componentDidUpdate(组件更新完成) 和 componentWillUnmount(组件将要卸载之前)的组合

语法:React.useEffect(()=>{},[])

当如果不希望在组件更新时再次执行,只希望在组件挂载时执行,则需要给useEffect方法设置第二个参数为数组

数组参数的含义:设置了哪些状态数据修改之后,才会执行回调,

分为两种情况:

  • 数组的参数如果没有任何值的话,表示什么状态数据都不更新,回调函数不执行
  • 当数组中传入state的状态数据,表示当这个状态数据被更新了的时候,才执行回调函数
import React, { useEffect, useState } from 'react'

export default function App() {
    //声明状态
    let [count, setCount] = useState(0);
    let [sex, setSex] = useState(true);
    //调用
    //第一个参数为函数,箭头函数或匿名函数均可
    //这个函数的作用等效于ComponentDidMount和ComponentDidUpdate
    //这个方法可以执行多次
    useEffect(function () {
        console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-1');
    });

    useEffect(function () {
        console.log('我既执行ComponentDidMount效果,也执行ComponentDidUpdate效果-2');
    });

    //当如果只想要ComponentDidMount效果,则需要给useEffect方法传入第二个参数,为数组
    //数组的参数如果没有任何元素的话,表示不更新状态

    /* useEffect(() => {
        console.log('我只执行ComponentDidMount效果');
    }, []) */

    //如果想要某一个状态数据改变的时候,需要执行,则将这个状态数据添加即可
    /* useEffect(() => {
        console.log('我只执行ComponentDidMount效果');
    }, [sex]) */

    //如果想要模拟ComponentwillUnmount
    //在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
    useEffect(() => {
        return () => {
            console.log('组件将要被卸载时...')
        }
    })

    let changeAdd = (num) => {
        return () => {
            count = count + num;
            setCount(count);
        }
    }
    let changeSex = () => {
        setSex(!sex);
    }
    return (
        <div>
            <ul>
                <li>
                    <p>数值:{count}</p>
                    <button onClick={changeAdd(10)}>新增</button>
                </li>
            </ul>
            <ul>
                <li>
                    <p>性别:{sex ? '男' : '女'}</p>
                    <button onClick={changeSex}>修改</button>
                </li>
            </ul>
        </div>
    )
}

但是使用useEffect()方法的时候有一个很重要的细节,那就是useEffect方法的第一个函数不能是一个异步函数

也就是说,不能出现async,原因是如果设置了async,返回值就成了promise对象,非一个函数了。

如果想要在useEffect()方法中添加异步代码,需要在函数中在单独声明一个函数

//如果想要模拟ComponentwillUnmount
//在脚手架中当尝试修改组件之后,react会将组件先卸载,然后再来一个新的重新挂载
useEffect(() => {
    return () => {
        async function main() {
            console.log('在这里可以执行异步代码逻辑...');
        }
        main();
    }
})

使用Hook 注意:

  1. 只在最顶层使用 Hook,不要在条件或循环中

  2. 只在React组件函数内部中调用 Hook, 不要在组件函数外部调用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/667735.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

宏景eHR SQL注入漏洞复现(CNVD-2023-08743)

0x01 产品简介 宏景eHR人力资源管理软件是一款人力资源管理与数字化应用相融合&#xff0c;满足动态化、协同化、流程化、战略化需求的软件。 0x02 漏洞概述 宏景eHR 存在SQL注入漏洞&#xff0c;未经过身份认证的远程攻击者可利用此漏洞执行任意SQL指令&#xff0c;从而窃取数…

android 如何分析应用的内存(七)——malloc hook

android 如何分析应用的内存&#xff08;七&#xff09; 接上文&#xff0c;介绍六大板块中的第二个————malloc hook 上一篇的自定义分配函数&#xff0c;常常只能解决当前库中的分配&#xff0c;而不能跟踪整个app中的分配。 为此&#xff0c;android的libc库&#xff…

正运动即将亮相2023年深圳激光展,助力个性化激光智能制造!

■展会名称&#xff1a; 第⼗六届深圳国际激光与智能装备、光子技术博览会&#xff08;以下简称“深圳激光展”&#xff09; ■展会日期 2023年6月27日-29日 ■展馆地点 深圳国际会展中心&#xff08;宝安新馆&#xff09; ■展位号 9D115 激光加工是一种基于光热效应的…

STM32的中断系统详解(嵌入式学习)

中断系统 1. 基本概念2. 中断的意义3. 中断处理过程处理过程过程详述 4. 中断体系结构5. NVIC概念主要功能 6. EXTI概念主要功能结构框图中断和事件的区别 7. 总结 1. 基本概念 中断是处理器中的一种机制&#xff0c;用于响应和处理突发事件或紧急事件。当发生中断时&#xff…

每日学术速递6.9

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Segment Anything in High Quality 标题&#xff1a;以高质量分割任何内容 作者&#xff1a;Lei Ke, Mingqiao Ye, Martin Danelljan, Yifan Liu, Yu-Wing Tai, Chi-Keung Tang, …

Reids分布式锁详细介绍原理和实现

Reids 分布式锁 问题描述 1、单体单机部署的系统被演化成分布式集群系统后 2、由于分布式系统多线程、多进程并且分布在不同机器上&#xff0c;这将使原单机部署情况下的并发控制锁策略失效 3、单纯的Java API 并不能提供分布式锁的能力 4、为了解决这个问题就需要一种跨J…

abd shell后,getevent退出方法

abd shell后&#xff0c;getevent退出方法 输入 exit 然后回车退出

一种很新的交互式智能标注技术

随着人工智能应用的大规模落地&#xff0c;数据标注市场在高速增长的同时&#xff0c;也面临着标注成本的挑战。据IDC报告显示&#xff1a;数据标注在AI应用开发过程中所耗费的时间占到了25%&#xff0c;部分医学类应用一条数据的标注成本甚至高达20元。数据精度的高要求、强人…

RocketMQ 环境搭建

环境&#xff1a;linux&#xff08;centos&#xff09; 或 windos&#xff1b; jdk 1.8 场景&#xff1a;rocket入门学习 时间&#xff1a;2023-04-20 吐槽&#xff1a;可能是本人学习能力不足&#xff0c;想使用docker搭建rocketmq 一直失败&#xff0c;可能是我想使用的比较新…

正排倒排,并不是 MySQL 的排序的全部!

引言 一个悠闲的上午&#xff0c;小航送了我&#xff0c;一袋坚果&#xff0c;他看我吃的正香&#xff0c;慢慢问道&#xff1a;”温哥&#xff0c;mysql的排序&#xff0c;有什么要注意的吗&#xff0c;不就是正排倒排吗&#xff1f;” 我一听他问我的问题&#xff0c;顿感坚…

软件测试简历如何包装?

首先明确的包装简历不等于欺骗&#xff0c;只是把你的最好一面展示出来&#xff0c;给别人一个好的映像&#xff1b;&#xff08;就相当于相亲&#xff0c;哈哈&#xff09; 无论如何包装简历&#xff0c;注意简历上的东西一定要会、一定要会、一定要会&#xff08;面试官一般…

Java框架-Spring

文章目录 1、你了解Spring IOC吗&#xff1f;2、SpringIOC的应用&#xff1f;3、SpringIOC的getBean方法的解析&#xff1f;4、面试题5、你了解Spring AOP吗&#xff1f;6、事务ACID特性7、事务传播 1、你了解Spring IOC吗&#xff1f; IoC&#xff08;Inversion of control&a…

C++编程启蒙-2——你适合学习编程吗?

英语差&#xff0c;数学孬&#xff0c;照样可以学好编程。但&#xff0c;如果你逻辑思维差&#xff0c;动力能力弱&#xff0c;那么学习编程真的会难上加难。本课用来帮助读者实现对逻辑思维与动手能力的自我判断&#xff0c;并给出了实际测试方案。 英语差&#xff0c;数学孬&…

15个常见的AI绘画网站推荐

无论你是专业的艺术家还是对人工智能绘画感兴趣的普通人&#xff0c;AI绘画网站都可以为你提供新的创作灵感和艺术体验&#xff0c;给艺术界带来更多的创新和可能性。以下是15个常见的AI绘画网站的介绍。 即时 AI 灵感 「即时 AI 灵感」是通过文字描述等方式生成精致图像的AI…

QGIS实现shape、geojson数据的矢量切片教程

能够实现矢量切片的办法有很多&#xff0c;可以使用geoserver&#xff0c;可以使用qgis&#xff0c;当然也可以自己写代码实现。这篇文章我们来介绍一下如何使用qgis完成shape数据的矢量切片。 首先我们还是要准备一份矢量数据。矢量数据的格式是shape文件或者是geojson文件都…

IDEA下载安装与使用

IDEA下载、安装与概述、使用 IDEA全称InteliJ IDEA&#xff0c;是用于Java语言开发的集成环境&#xff0c;它是业界公认的目前用于Java程序开发最好的工具 集成环境&#xff1a;把代码编写、编译、执行、调试等多种功能综合到一起的开发工具 1 IDEA的下载 官网链接&#xf…

第一章 基础算法(二)——高精度,前缀和与差分

文章目录 高精度运算高精度加法高精度减法高精度乘法高精度除法 前缀和二维前缀和 差分二维差分 高精度练习题791. 高精度加法792. 高精度减法793. 高精度乘法794. 高精度除法 前缀和练习题795. 前缀和796. 子矩阵的和 差分练习题797. 差分798. 差分矩阵 高精度运算 两个大数做…

Day37

思维导图 练习 1> 编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 a、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 b、和当前用户说“hello 用户名” c、显示您的机器名 hostname d、显示上一级目录中的所有文件的列表 e、显示…

Git 多账号多仓库配置 SSH

前言 在我们使用 Git 中&#xff0c;有时候会遇到多账号多仓库的情况&#xff0c;比如公司的 GitLab 和 GitHub&#xff0c;以及自己的 GitHub&#xff0c;这时候我们就需要配置多个 SSH 密钥来区分不同的账号和仓库 生成 SSH 密钥 根据你注册仓库的邮箱生成 SSH 密钥&#…

Kubeadm方式搭建K8s集群 1.27.0版本

目录 一、集群规划 二、系统初始化准备(所有节点同步操作) 三、安装并配置cri-docker插件 四、安装kubeadm&#xff08;所有节点同步操作&#xff09; 五、初始化集群 六、Node节点添加到集群 七、安装网络组件Calico 八、测试codedns解析可用性 一、集群规划 环境规划…