天天看点

07-React Hooks(路由组件懒加载, Context上下文, 组件优化...)

扩展

setState

(1). setState(stateChange, [callback])------对象式的setState
    1.stateChange为状态改变对象(该对象可以体现出状态的更改)
    2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
(2). setState(updater, [callback])------函数式的setState
    1.updater为返回stateChange对象的函数。
    2.updater可以接收到state和props。
    3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
总结:
    1.对象式的setState是函数式的setState的简写方式(语法糖)
    2.使用原则:
        (1).如果新状态不依赖于原状态 ===> 使用对象方式
        (2).如果新状态依赖于原状态 ===> 使用函数方式
        (3).如果需要在setState()执行后获取最新的状态数据, 
        要在第二个callback函数中读取      

LazyLoader

路由组件的懒加载

# 导入库
import React, {lazy,Suspense} from 'react';
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))

//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面      

Hooks

简介

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

useState

/**
 * 使用范围: 用于函数式组件, 使函数式组件具备state的能力
 * useState的使用方式
 * 1: 从react库中引入 useState 函数
 * 2: 使用函数创建值引用和方法引用
 *  2.1: const [count, setCount] = useState(0)
 *  2.2: 调用useState 入参为初次属性初始化的默认值
 *  2.3: 返回值为数组,一般使用结构的方式获取回来, 第一个引用为值对象, 第二个引用为该值对象的赋值函数
 * 3: 渲染方式, 直接通过 {count} 渲染
 * 4: 赋值方式: 调用赋值函数
 *  4.1: 入参为值对象修改 setCount(count+1)
 *  4.2: 入参为函数修改: setCount(count => count + 1) 函数会有一个入参为当前值对象, 然后需要返回一个新的值对象
 */
import React, {useState} from 'react';

function Index(props) {
    const [count, setCount] = useState(0)

    const add = () => {
        // setCount(count+1)
        setCount(count => count + 1)
    }
    return (
        <div>
            <h2>当前求和为:{count}</h2>
            <button onClick={add}>+1</button>
        </div>
    );
}

export default      

useEffect

/**
 * 使用范围: 用于函数式组件, 使函数式组件具备生命周期钩子的能力,可以看做是
 *  componentDidMount,componentDidUpdate,componentWillUnmount
 *  三个生命周期钩子函数的集合
 * useEffect的使用方式
 * 1: 从react库中引入 useEffect 函数
 * 2: 使用函数完成生命周期钩子函数
 * -:具体使用看下面注释
 *
 */
import React, {useState, useEffect} from 'react';

function Index(props) {

    // useState
    const [count, setCount] = useState(0)
    const [sum, setSum] = useState(0)
    const [he, setHe] = useState(0)

    /**
     * 实现componentDidMount
     * useEffect 第二个参数[] 什么也不写, 就是代表不监听任何state的变化, 只有在第一次渲染的时候执行
     */
    useEffect(() => {
        // setCount(count+1)
        // 实现count自动累加
        const timer = setInterval(() => {
            // 这里有个问题, 需要使用函数式入参, 不能直接使用值入参, 因为值入参是异步的, 函数的话会接受到上一次的值
            setCount(count => count + 1)
        }, 1000)
    }, [])

    /**
     * 实现componentDidMount+componentDidUpdate
     * useEffect 第二个参数[] 里面写了那些state的值对象, 当这些值对象发生变化时,就会执行这个函数
     */
    useEffect(() => {
        // 当count改变的时候sum自动加1
        if (count !== 0) {
            setSum(sum => sum + 1)
        }
    }, [count])

    /**
     * 实现componentDidMount+componentDidUpdate+componentWillUnmount
     * useEffect 函数, 可以返回一个函数, 这个返回的函数就是componentWillUnmount生命周期钩子, 所有清除定时器,取消订阅等操作就可以写在这个函数里面
     */
    useEffect(() => {
        // 当count改变的时候sum自动加1
        const timer = setInterval(() => {
            // 这里有个问题, 需要使用函数式入参, 不能直接使用值入参, 因为值入参是异步的, 函数的话会接受到上一次的值
            setHe(he => he + 1)
        }, 1000)
        return () => {
            clearInterval(timer)
        }
    }, [])

    return (
        <div>
            <h2>当前求和为:{count}</h2>
            <h2>当前求和为:{sum}</h2>
            <h2>当前求和为:{he}</h2>
        </div>
    );
}

export default      

useRef

/**
 * 使用范围: 用于函数式组件, 使函数式组件具备React.createRef的能力
 * useRef的使用方式
 * 1: 从react库中引入useRef函数
 * 2: 使用函数创建属性 const myRef = useRef()
 * 3: 绑定到组件 <input ref={myRef} type="text"/>
 * 4: 获取值 myRef.current.value
 */
import React, {useRef} from 'react';

function Index(props) {

    const myRef = useRef()

    const show = () => {
        console.log(myRef.current.value)
    }

    return (
        <div>
            <input ref={myRef} type="text"/>
            <button onClick={show}>显示</button>
        </div>
    );
}

export default      

Fragment

/**
 * Fragment : 代码片段标签, 在React渲染时会被丢弃
 * 使用方式:
 * 1: 从react库中引入
 * 2: 一般包裹在最外层
 * 3: 只接受唯一一个属性 key
 * 4: 如不过想写, 可以使用空标签替换 <></>
 */
import React, {Fragment} from 'react';

function Index(props) {
    return (
        <Fragment key={'fg'}>
            <h2>代码片段:</h2>
        </Fragment>
    );
}

export default      

Context+useContext(Hooks)

/**
 * Context: 上下文对象, 一般用于多层次组件传递值
 * 使用方式:
 * 1: 从react中引入React
 * 2: 创建: const UserNameContext = createContext('dance')
 * -: 看下方注释
 */
import React, {Component, useState, useContext, createContext} from 'react';

// 创建Context对象
const UserNameContext = createContext('dance')

function Main(props) {
    const [userName, setUserName] = useState('tom');
    return (
        <>
            <h2>我是Main组件</h2>
            <h3>我的用户名是:{userName}</h3>
            <hr/>
            {/* 通过value属性传入参数, 所有的子组件就都可以获取到context */}
            <UserNameContext.Provider value={userName}>
                <A/>
            </UserNameContext.Provider>
        </>
    );
}

function A(props) {
    return (
        <>
            <h2>我是A组件</h2>
            {/* 通过标签Consumer获取,并渲染 */}
            <UserNameContext.Consumer>
                {
                    value => (<h3>接受到的用户名是:{value}</h3>)
                }
            </UserNameContext.Consumer>
            <hr/>
            <B/>
        </>
    );
}

/**
 * 函数式组件接受Context
 */
function B(props) {
    // 通过useContext函数获取,并渲染
    let context = useContext(UserNameContext)
    return (
        <>
            <h2>我是B组件</h2>
            <h3>接受到的用户名是:{context}</h3>
            <C/>
        </>
    );
}

/**
 * 类组件 接受Context
 */
class C extends Component {
    // 类组件通过属性获取,并渲染
    static contextType = UserNameContext

    render() {
        return (
            <>
                <h2>我是C组件</h2>
                <h3>接受到的用户名是:{this.context}</h3>
            </>
        );
    }
}

export default      

组件优化

Component的两个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

优化

要让组件, 只有当组件的state或props数据发生改变时才重新render()

因为Component中的shouldComponentUpdate()总是返回true

解决办法

办法1: 
    重写shouldComponentUpdate()方法
    比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
    使用PureComponent
    PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
    注意: 
        只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
        不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化      

render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
    使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
    使用children props: 通过组件标签体传入结构
    使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性      

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到      

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}      

错误边界

  • 理解:
  • 错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
  • 特点:
  • 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
  • 使用方式:
  • getDerivedStateFromError配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    // 在render之前触发
    // 返回新的state
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台去
    console.log(error, info);
}      

组件通信方式总结

组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式:

1.props:
  (1).children props
  (2).render props
2.消息订阅-发布:
  pubs-sub、event等等
3.集中式管理:
  redux、dva等等
4.conText:
  生产者-消费者模式      

比较好的搭配方式:

父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)