React 入门学习(十七)-- React 扩展
Posted 小丞同学
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React 入门学习(十七)-- React 扩展相关的知识,希望对你有一定的参考价值。
📢 大家好,我是小丞同学,一名大二的前端爱好者
📢 这篇文章是学习 React扩展部分的学习笔记
📢 非常感谢你的阅读,不对的地方欢迎指正 🙏
📢 愿你忠于自己,热爱生活
引言
学到这里 React 已经学的差不多了,接下来就学习一些 React 扩展内容,可以帮助我们更好的开发和理解,这部分的知识还有很多的东西可以探寻,比如:网红 React-Hook,就是我们需要注意的地方,打了 100 多集的类式组件,出来一个 hooks ,现在用函数式组件偏多了…
所以 Hooks 就需要我们深入的学习一下了,下面我们就一起来看看扩展部分有哪些内容吧
1. setState
对象式 setState
首先在我们以前的认知中,setState
是用来更新状态的,我们一般给它传递一个对象,就像这样
this.setState(
count: count + 1
)
这样每次更新都会让 count
的值加 1。这也是我们最常做的东西
这里我们做一个案例,点我加 1,一个按钮一个值,我要在控制台输出每次的 count
的值
那我们需要在控制台输出,要如何实现呢?
我们会考虑在 setState
更新之后 log
一下
add = () =>
const count = this.state
this.setState(
count: count + 1
)
console.log(this.state.count);
因此可能会写出这样的代码,看起来很合理,在调用完 setState
之后,输出 count
我们发现显示的 count
和我们控制台输出的 count
值是不一样的
这是因为,我们调用的 setState
是同步事件,但是它的作用是让 React 去更新数据,而 React 不会立即的去更新数据,这是一个异步的任务,因此我们输出的 count
值会是状态更新之前的数据。“React 状态更新是异步的”
那我们要如何实现同步呢?
其实在 setState
调用的第二个参数,我们可以接收一个函数,这个函数会在状态更新完毕并且界面更新之后调用,我们可以试试
add = () =>
const count = this.state
this.setState(
count: count + 1
, () =>
console.log(this.state.count)
)
我们将 setState
填上第二个参数,输出更新后的 count
值
这样我们就能成功的获取到最新的数据了,如果有这个需求我们可以在第二个参数输出噢~
函数式 setState
这种用法我也是第一次见,函数式的 setState
也是接收两个参数
第一个参数是 updater
,它是一个能够返回 stateChange
对象的函数
第二个参数是一个回调函数,用于在状态更新完毕,界面也更新之后调用
与对象式 setState
不同的是,我们传递的第一个参数 updater
可以接收到2个参数 state
和 props
我们尝试一下
add = () =>
this.setState((state) => ( count: state.count + 1 ))
我们也成功的实现了
我们在第一个参数中传入了一个函数,这个函数可以接收到 state
,我们通过更新 state
中的 count
值,来驱动页面的更新
利用函数式 setState
的优势还是很不错的,可以直接获得 state
和 props
可以理解为对象式的
setState
是函数式setState
的语法糖
2. LazyLoad
懒加载在 React 中用的最多的就是路由组件了,页面刷新时,所有的页面都会重新加载,这并不是我们想要的,我们想要实现点击哪个路由链接再加载即可,这样避免了不必要的加载
我们可以发现,我们页面一加载时,所有的路由组件都会被加载
如果我们有 100 个路由组件,但是用户只点击了几个,这就会有很大的消耗,因此我们需要做懒加载处理,我们点击哪个时,才去加载哪一个
首先我们需要从 react
库中暴露一个 lazy
函数
import React, Component ,lazy from 'react';
然后我们需要更改引入组件的方式
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
采用 lazy
函数包裹
我们会遇到这样的错误,提示我们用一个标签包裹
这里是因为,当我们网速慢的时候,路由组件就会有可能加载不出来,页面就会白屏,它需要我们来指定一个路由组件加载的东西,相对于 loading
<Suspense fallback=<h1>loading</h1>>
<Route path="/home" component=Home></Route>
<Route path="/about" component=About></Route>
</Suspense>
在做这个案例的时候,一定不要设置重定向的东西,所有的路由我们要点击再加载
初次登录页面的时候
注意噢,这些文件都不是路由组件,当我们点击了对应组件之后才会加载
从上图我们可以看出,每次点击时,才会去请求 chunk
文件
那我们更改写的 fallback
有什么用呢?它会在页面还没有加载出来的时候显示
注意:因为 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 案例
这里利用了一个 Hook :useState
它让函数式组件能够维护自己的 state
,它接收一个参数,作为初始化 state
的值,赋值给 count
,因此 useState
的初始值只有第一次有效,它所映射出的两个变量 count
和 setCount
我们可以理解为 setState
来使用
useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数
我们先看看控制台输出的什么
count
是初始化的值,而 setCount
就像是一个 action
对象驱动状态更新
我们可以通过 setCount
来更新 count
的值
setCount(count + 1)
useEffect
在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如 componentDidMount
,能够在组件挂载完成后执行一些东西
在函数式组件中也可以实现,它采用的是 effectHook
,它的语法更加的简单,同时融合了 componentDidUpdata
生命周期,极大的方便了我们的开发
React.useEffect(() => console.log('被调用了');)
由于函数的特性,我们可以在函数中随意的编写函数,这里我们调用了 useEffect
函数,这个函数有多个功能
当我们像上面代码那样使用时,它相当于 componentDidUpdata
和 componentDidMount
一同使用,也就是在组件挂载和组件更新的时候都会调用这个函数
它还可以接收第二个参数,这个参数表示它要监测的数据,也就是他要监视哪个数据的变化
当我们不需要监听任何状态变化的时候,我们可以就传递一个空数组,这样它就能当作 componentMidMount
来使用
React.useEffect(() => console.log('被调用了');, [])
这样我们只有在组件第一次挂载的时候触发
当然当页面中有多个数据源时,我们也可以选择个别的数据进行监测以达到我们想要的效果
React.useEffect(() => console.log('被调用了');, [count])
这样,我们就只监视 count 数据的变化
当我们想要在卸载一个组件之前进行一些清除定时器的操作,在类式组件中,我们会调用生命周期钩子 componentDidUnmount
来实现,在函数式组件中,我们的写法更为简单,我们直接在 useEffect
的第一个参数的返回值中实现即可
也就是说,第一个参数的函数体相当于 componentDidMount
返回体相当于 componentDidUnmount
,这样我们就能实现在组件即将被卸载时输出一些东西了
实现卸载
function unmount() ReactDOM.unmountComponentAtNode(document.getElementById("root"))
卸载前输出
React.useEffect(() => console.log('被调用了'); return () => console.log('我要被卸载了'); , [count])
实现了在组件即将被卸载的时候输出
因此 useEffect
相当于三个生命周期钩子,componentDidMount
、componentDidUpdata
、componentDidUnmount
useRef
当我们想要获取组件内的信息时,在类式组件中,我们会采用 ref
的方式来获取。在函数式组件中,我们可以采用也可以采用 ref
但是,我们需要采用 useRef
函数来创建一个 ref 容器,这和 createRef
很类似。
<input type="text" ref=myRef />
获取 ref 值
function show() alert(myRef.current.value)
即可成功的获取到 input 框中的值
4. Fragment
我们编写组件的时候每次都需要采用一个 div
标签包裹,才能让它正常的编译,但是这样会引发什么问题呢?我们打开控制台看看它的层级
它包裹了几层无意义的 div 标签,我们可以采用 Fragment
来解决这个问题
首先,我们需要从 react 中暴露出 Fragment
,将我们所写的内容采用 Fragment
标签进行包裹,当它解析到 Fragment
标签的时候,就会把它去掉
这样我们的内容就直接挂在了 root
标签下
同时采用空标签,也能实现,但是它不能接收任何值,而
Fragment
能够接收 1 个值key
5. Context
仅适用于类式组件
当我们想要给子类的子类传递数据时,前面我们讲过了 redux 的做法,这里介绍的 Context 我觉得也类似于 Redux
首先我们需要引入一个 MyContext
组件,我们需要引用MyContext
下的 Provider
const MyContext = React.createContext();const Provider = MyContext;
用 Provider
标签包裹 A组件内的 B 组件,并通过 value
值,将数据传递给子组件,这样以 A 组件为父代组件的所有子组件都能够接受到数据
<Provider value= username, age > <B /></Provider>
但是我们需要在使用数据的组件中引入 MyContext
static contextType = MyContext;
在使用时,直接从 this.context
上取值即可
const username,age = this.context
适用于函数和类式组件
由于函数式组件没有自己 this
,所以我们不能通过 this.context
来获取数据
这里我们需要从 Context
身上暴露出一个 Consumer
const Provider ,Consumer = MyContext;
然后通过 value
取值即可
function C() return ( <div> <h3>我是C组件,我从A接收到的数据 </h3> <Consumer> (value) => return `$value.username,年龄是$value.age`; </Consumer> </div> );
因此想要在函数式组件中使用,需要引入
Consumer
6. PureComponent
在我们之前一直写的代码中,我们一直使用的Component
是有问题存在的
- 只要执行
setState
,即使不改变状态数据,组件也会调用render
- 当前组件状态更新,也会引起子组件
render
而我们想要的是只有组件的 state
或者 props
数据发生改变的时候,再调用 render
我们可以采用重写 shouldComponentUpdate
的方法,但是这个方法不能根治这个问题,当状态很多时,我们没有办法增加判断
我们可以采用 PureComponent
我们可以从 react
身上暴露出 PureComponent
而不使用 Component
import React, PureComponent from 'react'
就这~听了半天结果就只一个 PureComponent
PureComponent
会对比当前对象和下一个状态的 prop
和 state
,而这个比较属于浅比较,比较基本数据类型是否相同,而对于引用数据类型,比较的是它的引用地址是否相同,这个比较与内容无关
7. render props
采用 render props 技术,我们可以像组件内部动态传入带有内容的结构
当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过
this.props.children
来获取
例如:
<A>hello</A>
这个 hello 我们就可以通过 children 来获取
而我们所说的 render props 就是在组件标签中传入一个 render 方法,又因为属于 props ,因而被叫做了 render props
<A render=(name) => <C name=name /> />
你可以把 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
我们可以将 hasError
配置到状态当中,当 hasError
状态改变成 error
时,表明有错误发生,我们需要在组件中通过判断 hasError
值,来指定是否显示子组件
this.state.hasError ? <h2>出错啦</h2> : <Child />
在服务器中启动,才能正常看到效果
可以在 componentDidCatch
中统计错误次数,通知编码人员进行 bug 解决
9. 组件通信方式总结
- props
- children props
- render props
- 消息发布订阅
- 利用 pubsub 库来实现
- 集中式状态管理
- redux
- conText
- 生成者-消费者
选择方式
父子组件采用:props
兄弟组件采用:消息的发布订阅、redux
祖孙组件:消息发布订阅、redux、context
以上是关于React 入门学习(十七)-- React 扩展的主要内容,如果未能解决你的问题,请参考以下文章