react-扩展

📢 大家好,我是小丞同学,一名大二的前端爱好者

📢 这篇文章是学习 React扩展部分的学习笔记

📢 非常感谢你的阅读,不对的地方欢迎指正 🙏

📢 愿你忠于自己,热爱生活

# 引言

学到这里 React 已经学的差不多了,接下来就学习一些 React 扩展内容,可以帮助我们更好的开发和理解,这部分的知识还有很多的东西可以探寻,比如:网红 React-Hook,就是我们需要注意的地方,打了 100 多集的类式组件,出来一个 hooks ,现在用函数式组件偏多了.............

所以 Hooks 就需要我们深入的学习一下了,下面我们就一起来看看扩展部分有哪些内容吧

# 1. setState

# 对象式 setState

首先在我们以前的认知中,setState 是用来更新状态的,我们一般给它传递一个对象,就像这样

this.setState({
    count: count + 1
})
1
2
3

这样每次更新都会让 count 的值加 1。这也是我们最常做的东西

这里我们做一个案例,点我加 1,一个按钮一个值,我要在控制台输出每次的 count 的值

react-extension-demo1

那我们需要在控制台输出,要如何实现呢?

我们会考虑在 setState 更新之后 log 一下

add = () => {
    const { count } = this.state
    this.setState({
        count: count + 1
    })
    console.log(this.state.count);
}
1
2
3
4
5
6
7

因此可能会写出这样的代码,看起来很合理,在调用完 setState 之后,输出 count

react-extension-demo1-2

我们发现显示的 count 和我们控制台输出的 count 值是不一样的

这是因为,我们调用的 setState 是同步事件,但是它的作用是让 React 去更新数据,而 React 不会立即的去更新数据,这是一个异步的任务,因此我们输出的 count 值会是状态更新之前的数据。“React 状态更新是异步的

那我们要如何实现同步呢?

其实在 setState 调用的第二个参数,我们可以接收一个函数,这个函数会在状态更新完毕并且界面更新之后调用,我们可以试试

add = () => {
    const { count } = this.state
    this.setState({
        count: count + 1
    }, () => {
        console.log(this.state.count)
    })
}
1
2
3
4
5
6
7
8

我们将 setState 填上第二个参数,输出更新后的 count

react-extension-demo1-3

这样我们就能成功的获取到最新的数据了,如果有这个需求我们可以在第二个参数输出噢~

# 函数式 setState

这种用法我也是第一次见,函数式的 setState 也是接收两个参数

第一个参数是 updater ,它是一个能够返回 stateChange 对象的函数

第二个参数是一个回调函数,用于在状态更新完毕,界面也更新之后调用

与对象式 setState 不同的是,我们传递的第一个参数 updater 可以接收到2个参数 stateprops

我们尝试一下

add = () => {
    this.setState((state) => ({ count: state.count + 1 }))
}
1
2
3

react-extension-demo2-1

我们也成功的实现了

我们在第一个参数中传入了一个函数,这个函数可以接收到 state ,我们通过更新 state 中的 count 值,来驱动页面的更新

利用函数式 setState 的优势还是很不错的,可以直接获得 stateprops

可以理解为对象式的 setState 是函数式 setState 的语法糖

# 2. LazyLoad

懒加载在 React 中用的最多的就是路由组件了,页面刷新时,所有的页面都会重新加载,这并不是我们想要的,我们想要实现点击哪个路由链接再加载即可,这样避免了不必要的加载

react-extension-demo2-2

我们可以发现,我们页面一加载时,所有的路由组件都会被加载

如果我们有 100 个路由组件,但是用户只点击了几个,这就会有很大的消耗,因此我们需要做懒加载处理,我们点击哪个时,才去加载哪一个

首先我们需要从 react 库中暴露一个 lazy 函数

import React, { Component ,lazy} from 'react';
1

然后我们需要更改引入组件的方式

const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
1
2

采用 lazy 函数包裹

image-20210911114307684

我们会遇到这样的错误,提示我们用一个标签包裹

这里是因为,当我们网速慢的时候,路由组件就会有可能加载不出来,页面就会白屏,它需要我们来指定一个路由组件加载的东西,相对于 loading

<Suspense fallback={<h1>loading</h1>}>
    <Route path="/home" component={Home}></Route>
    <Route path="/about" component={About}></Route>
</Suspense>
1
2
3
4

在做这个案例的时候,一定不要设置重定向的东西,所有的路由我们要点击再加载

初次登录页面的时候

image-20210911115542647

注意噢,这些文件都不是路由组件,当我们点击了对应组件之后才会加载

react-extension-lazyload-2

从上图我们可以看出,每次点击时,才会去请求 chunk 文件

那我们更改写的 fallback 有什么用呢?它会在页面还没有加载出来的时候显示

react-extension-lazyload-3

注意:因为 loading 是作为一个兜底的存在,因此 loading 是 必须提前引入的,不能懒加载

# 3. Hooks

# useState

hooks 解决了函数式组件和类式组件的差异,让函数式组件拥有了类式组件所拥有的 state ,同时新增了一些 API ,让函数式组件,变得更加的灵活

首先我们需要明确一点,函数式组件没有自己的 this

function Demo() {
    const [count, setCount] = React.useState(0)
    console.log(count, setCount);
    function add() {
        setCount(count + 1)
    }
    return (
        <div>
            <h2>当前求和为:{count}</h2>
            <button onClick={add}>点我加1</button>
        </div>
    )
}
export default Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14

利用函数式组件完成的 点我加1 案例

这里利用了一个 Hook :useState

它让函数式组件能够维护自己的 state ,它接收一个参数,作为初始化 state 的值,赋值给 count,因此 useState 的初始值只有第一次有效,它所映射出的两个变量 countsetCount 我们可以理解为 setState 来使用

useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数

我们先看看控制台输出的什么

image-20210911123011304

count 是初始化的值,而 setCount 就像是一个 action 对象驱动状态更新

我们可以通过 setCount 来更新 count 的值

setCount(count + 1)
1

# useEffect

在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount ,能够在组件挂载完成后执行一些东西

在函数式组件中也可以实现,它采用的是 effectHook ,它的语法更加的简单,同时融合了 componentDidUpdata 生命周期,极大的方便了我们的开发

React.useEffect(() => {
    console.log('被调用了');
})
1
2
3

由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect 函数,这个函数有多个功能

当我们像上面代码那样使用时,它相当于 componentDidUpdatacomponentDidMount 一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数

react-extension-hook-1

它还可以接收第二个参数,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化

当我们不需要监听任何状态变化的时候,我们可以就传递一个空数组,这样它就能当作 componentMidMount 来使用

React.useEffect(() => {
    console.log('被调用了');
}, [])
1
2
3

这样我们只有在组件第一次挂载的时候触发

当然当页面中有多个数据源时,我们也可以选择个别的数据进行监测以达到我们想要的效果

React.useEffect(() => {
    console.log('被调用了');
}, [count])
1
2
3

这样,我们就只监视 count 数据的变化

当我们想要在卸载一个组件之前进行一些清除定时器的操作,在类式组件中,我们会调用生命周期钩子 componentDidUnmount 来实现,在函数式组件中,我们的写法更为简单,我们直接在 useEffect 的第一个参数的返回值中实现即可 也就是说,第一个参数的函数体相当于 componentDidMount 返回体相当于 componentDidUnmount ,这样我们就能实现在组件即将被卸载时输出一些东西了

实现卸载

function unmount() {
    ReactDOM.unmountComponentAtNode(document.getElementById("root"))
}
1
2
3

卸载前输出

React.useEffect(() => {
    console.log('被调用了');
    return () => {
        console.log('我要被卸载了');
    }
}, [count])
1
2
3
4
5
6

react-extension-hook-2

实现了在组件即将被卸载的时候输出

因此 useEffect 相当于三个生命周期钩子,componentDidMountcomponentDidUpdatacomponentDidUnmount

# useRef

当我们想要获取组件内的信息时,在类式组件中,我们会采用 ref 的方式来获取。在函数式组件中,我们可以采用也可以采用 ref 但是,我们需要采用 useRef 函数来创建一个 ref 容器,这和 createRef 很类似。

<input type="text" ref={myRef} />
1

获取 ref 值

function show() {
    alert(myRef.current.value)
}
1
2
3

即可成功的获取到 input 框中的值

# 4. Fragment

我们编写组件的时候每次都需要采用一个 div 标签包裹,才能让它正常的编译,但是这样会引发什么问题呢?我们打开控制台看看它的层级

image-20210911151643934

它包裹了几层无意义的 div 标签,我们可以采用 Fragment 来解决这个问题

首先,我们需要从 react 中暴露出 Fragment ,将我们所写的内容采用 Fragment 标签进行包裹,当它解析到 Fragment 标签的时候,就会把它去掉

image-20210911152037120

这样我们的内容就直接挂在了 root 标签下

同时采用空标签,也能实现,但是它不能接收任何值,而 Fragment 能够接收 1 个值key

# 5. Context

# 仅适用于类式组件

当我们想要给子类的子类传递数据时,前面我们讲过了 redux 的做法,这里介绍的 Context 我觉得也类似于 Redux

首先我们需要引入一个 MyContext 组件,我们需要引用MyContext 下的 Provider

const MyContext = React.createContext();
const { Provider } = MyContext;
1
2

Provider 标签包裹 A组件内的 B 组件,并通过 value 值,将数据传递给子组件,这样以 A 组件为父代组件的所有子组件都能够接受到数据

<Provider value={{ username, age }}>
    <B />
</Provider>
1
2
3

但是我们需要在使用数据的组件中引入 MyContext

static contextType = MyContext;
1

在使用时,直接从 this.context 上取值即可

const {username,age} = this.context
1

# 适用于函数和类式组件

由于函数式组件没有自己 this ,所以我们不能通过 this.context 来获取数据

这里我们需要从 Context 身上暴露出一个 Consumer

const { Provider ,Consumer} = MyContext;
1

然后通过 value 取值即可

function C() {
  return (
    <div>
      <h3>我是C组件,我从A接收到的数据 </h3>
      <Consumer>
        {(value) => {
          return `${value.username},年龄是${value.age}`;
        }}
      </Consumer>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12

image-20210911161103300

因此想要在函数式组件中使用,需要引入 Consumer

# 6. PureComponent

在我们之前一直写的代码中,我们一直使用的Component 是有问题存在的

  1. 只要执行 setState ,即使不改变状态数据,组件也会调用 render
  2. 当前组件状态更新,也会引起子组件 render

而我们想要的是只有组件的 state 或者 props 数据发生改变的时候,再调用 render

我们可以采用重写 shouldComponentUpdate 的方法,但是这个方法不能根治这个问题,当状态很多时,我们没有办法增加判断

我们可以采用 PureComponent

我们可以从 react 身上暴露出 PureComponent 而不使用 Component

import React, { PureComponent } from 'react'
1

就这~听了半天结果就只一个 PureComponent

PureComponent 会对比当前对象和下一个状态的 propstate ,而这个比较属于浅比较,比较基本数据类型是否相同,而对于引用数据类型,比较的是它的引用地址是否相同,这个比较与内容无关

# 7. render props

采用 render props 技术,我们可以像组件内部动态传入带有内容的结构

当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过 this.props.children 来获取

例如:

<A>hello</A>
1

这个 hello 我们就可以通过 children 来获取

而我们所说的 render props 就是在组件标签中传入一个 render 方法,又因为属于 props ,因而被叫做了 render props

<A render={(name) => <C name={name} />} />
1

你可以把 render 看作是 props,只是它有特殊作用,当然它也可以用其他名字来命名

在上面的代码中,我们需要在 A 组件中预留出 C 组件渲染的位置 在需要的位置上加上{this.props.render(name)}

那我们在 C 组件中,如何接收 A 组件传递的 name 值呢?通过 this.props.name 的方式

# 8. ErrorBoundary

当不可控因素导致数据不正常时,我们不能直接将报错页面呈现在用户的面前,由于我们没有办法给每一个组件、每一个文件添加判断,来确保正常运行,这样很不现实,因此我们要用到错误边界技术

错误边界就是让这块组件报错的影响降到最小,不要影响到其他组件或者全局的正常运行

例如 A 组件报错了,我们可以在 A 组件内添加一小段的提示,并把错误控制在 A 组件内,不影响其他组件

  • 我们要对容易出错的组件的父组件做手脚,而不是组件本身

我们在父组件中通过 getDerivedStateFromError 来配置子组件出错时的处理函数

static getDerivedStateFromError(error) {
    console.log(error);
    return { hasError: error }
}
1
2
3
4

我们可以将 hasError 配置到状态当中,当 hasError 状态改变成 error 时,表明有错误发生,我们需要在组件中通过判断 hasError 值,来指定是否显示子组件

{this.state.hasError ? <h2>出错啦</h2> : <Child />}
1

在服务器中启动,才能正常看到效果

可以在 componentDidCatch 中统计错误次数,通知编码人员进行 bug 解决

# 9. 组件通信方式总结

  1. props
    • children props
    • render props
  2. 消息发布订阅
    • 利用 pubsub 库来实现
  3. 集中式状态管理
    • redux
  4. conText
    • 生成者-消费者

选择方式

父子组件采用:props

兄弟组件采用:消息的发布订阅、redux

祖孙组件:消息发布订阅、redux、context

lastUpdate: 5/22/2022, 4:34:51 PM