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

总结

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

  • 非受控组件

表单项不与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

父组件内容变化时触发
03、非受控组件与受控组件、高阶函数、prop-types、生命周期、hook_第1张图片

子组件内容变化触发:

03、非受控组件与受控组件、高阶函数、prop-types、生命周期、hook_第2张图片

例如:

  • 清空定时器
  • 取消订阅

流程: 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 + '
'
; } 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了

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

先来看一个案例:

03、非受控组件与受控组件、高阶函数、prop-types、生命周期、hook_第3张图片

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

父组件内容变化时触发
03、非受控组件与受控组件、高阶函数、prop-types、生命周期、hook_第4张图片

子组件内容变化触发:

03、非受控组件与受控组件、高阶函数、prop-types、生命周期、hook_第5张图片

例如:

  • 清空定时器
  • 取消订阅

流程: 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 + '
'
; } 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,react.js,javascript,前端)