React重点知识拓展,含Hooks、路由懒加载等
第7章:React扩展
一、setState
1.setState更新状态的2种写法
-
setState(stateChange, [callback])
------对象式的setState- stateChange为状态改变的对象(该对象可以体现出状态的更改)
- callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
-
setState(updater, [callback])
------函数式的setState- updater为一个函数,返回stateChange对象。
- updater可以接收到state和props。
- callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
2. 总结
-
对象式的setState是函数式的setState的简写方式(语法糖)
-
使用原则:
- 如果新状态不依赖于原状态 ===> 使用对象方式
- 如果新状态依赖于原状态 ===> 使用函数方式
- 如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取
-
setState更新状态的动作是异步的
我们会考虑在
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 状态更新是异步的”
二、lazyLoad
1. 路由组件的lazyLoad
懒加载在 React 中用的最多的就是路由组件了,页面刷新时,所有的组件都会重新加载,这并不是我们想要的,我们想要实现点击哪个路由链接再加载即可,这样避免了不必要的加载。若不使用路由懒加载技术,我们页面一加载时,所有的路由组件都会被加载。
-
首先我们需要从
react
库中暴露一个lazy
函数通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
import React, { Component ,lazy} from 'react';
-
然后我们需要更改引入组件的方式
// import Home from "./Home"; // import About from "./About"; const Home = lazy(() => import("./Home")); const About = lazy(() => import("./About"));
-
使用Suspense标签包括要显示的路由组件(Route标签),当我们网速慢的时候,路由组件就会有可能加载不出来,页面就会白屏,它需要我们使用fallback属性来指定一个路由组件加载的东西,相对于 loading
<Suspense fallback={<h1>loading</h1>}> <Route path="/home" component={Home}></Route> <Route path="/about" component={About}></Route> </Suspense>
-
注意:因为 loading 是作为一个兜底的存在,因此 loading 是 必须提前引入的,不能懒加载
三、Hooks
React Hook/Hooks是什么?
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以让你在函数组件中使用 state 以及其他的 React 特性
三个常用的Hook
- State Hook: React.useState()
- Effect Hook: React.useEffect()
- Ref Hook: React.useRef()
1. State Hook
State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
-
首先我们需要明确一点,由于函数处于严格模式,函数式组件没有自己的
this
-
语法: const [xxx, setXxx] = React.useState(initValue)
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
-
useState()说明:它让函数式组件能够维护自己的
state
- 它接收一个参数,作为初始化
state
的值,useState
的初始值只有第一次有效 - useState 能够返回一个数组,第一个元素是 state ,第二个是更新 state 的函数
- 更新状态函数setXxx的两种写法
setXxx(newValue)
: 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值setXxx(value => newValue)
: 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
- 它接收一个参数,作为初始化
2. Effect Hook
在类式组件中,提供了一些声明周期钩子给我们使用,我们可以在组件的特殊时期执行特定的事情,例如
componentDidMount
,能够在组件挂载完成后执行一些东西在函数式组件中也可以实现,它采用的是
effectHook
,它的语法更加的简单,同时融合了componentDidMount
、componentDidUpdata
和componentWillUnmount
生命周期,极大的方便了我们的开发
-
Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
-
React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
-
(3). 语法和说明:
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 }}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
-
可以把 useEffect Hook 看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
-
React.useEffect说明
- 第一个参数是一个回调函数,第二个参数是一个数组
- 第二个参数不写:所有state更新时,执行回调函数
- 添加第二个参数(数组):数组中对应的state更新时,执行回调函数,和Vue中的watch侦听类似
- 若为空数组
[]
,相当于componentDidMount
,回调函数只会在第一次render()
后执行 - 返回值是一个函数,该函数会在组件被卸载时执行,相当于
componentWillUnmount()
React.useEffect(() => { let timer = setInterval(() => { setCount((count) => count + 1); }, 1000); // 返回componentWillUnmount return () => { clearInterval(timer); }; }, []);
3. Ref Hook
当我们想要获取标签内的信息时,在类式组件中,我们会采用
ref
的方式来获取。在函数式组件中,我们可以采用也可以采用ref
但是,我们需要采用useRef
函数来创建一个 ref 容器,这和createRef
很类似。
-
使用useRef函数创建ref容器
const myRef = useRef()
-
给要获取的结点打ref标签
<input type="text" ref={myRef} />
-
获取input框输入的值
function show() { alert(myRef.current.value) }
-
重点是:useRef函数的使用
- 语法: const refContainer = useRef()
- 作用:保存标签对象,功能与React.createRef()一样
四、Fragment
-
我们编写组件的时候每次都需要采用一个
div
标签包裹,才能让它正常的编译,但是这样会引发什么问题呢?
它包裹了几层无意义的 div 标签,我们可以采用Fragment
来解决这个问题 -
我们需要从 react 中暴露出
Fragment
,将我们所要去掉的div采用Fragment
标签进行替换,当它解析到Fragment
标签的时候,就会把Fragment
去掉 -
同时采用空标签,也能实现,但是它不能接收任何值,而
Fragment
能够接收唯一一个 属性key
-
使用:
<Fragment key={id}><Fragment>
、<></>
-
作用:可以不用必须有一个真实的DOM根标签了
五、Context
一种组件间通信方式,常用于【祖组件】与【后代组件】间通信
1. 仅适用于类式组件
-
首先我们需要全局创建一个
MyContext
对象,我们需要引用MyContext
下的Provider
// 创建Context容器对象 const MyContext = React.createContext(); const { Provider } = MyContext;
-
渲染子组件时,外面包裹
Provider
, 通过value属性给后代组件传递数据<Provider value={{ username, age }}> <B /> </Provider>
-
在需要使用数据的后代组件,引入
MyContext
// 声明context static contextType = MyContext;
-
在使用数据时,直接从
this.context
上取值即可const {username,age} = this.context
2. 适用于函数式和类式组件
-
首先我们需要全局创建一个
MyContext
对象,我们需要引用MyContext
下的Provider
和Consumer
// 创建Context容器对象 const MyContext = React.createContext(); const { Provider , Consumer } = MyContext;
-
渲染子组件时,还是一样,外面包裹
Provider
, 通过value属性给后代组件传递数据 -
在需要使用数据的子组件中,使用
<Consumer></Consumer>
包裹,通过value
取值即可function C() { return ( <div> <h3>我是C组件,我从A接收到的数据 </h3> <Consumer> {(value) => { return `${value.username},年龄是${value.age}`; }} </Consumer> </div> ); }
3. 注意
在应用开发中一般不用context, 一般都它的封装react插件
六、组件优化
1. Component的2个问题
-
只要执行setState(),即使不改变状态数据, 组件也会重新render()
-
只当前组件重新render(), 就会自动重新render子组件 ==> 效率低
2. 效率高的做法
- 只有当组件的state或props数据发生改变时才重新render()
3. 原因
- Component中的shouldComponentUpdate()总是返回true
4. 解决
-
办法1:
-
重写
shouldComponentUpdate()
方法 -
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
-
如下代码
shouldComponentUpdate(nextProps, nextState) { // console.log(this.props, this.state); // 目前的props和state // console.log(nextProps, nextState); // 接下来要变化的目标props,目标state return !(this.state.carName === nextState.carName); }
-
-
办法2:
-
使用PureComponent
从
react
身上暴露出PureComponent
而不使用Component
import React, { PureComponent } from 'react'
-
PureComponent重写了
shouldComponentUpdate()
,只有state或props数据有变化才返回true -
注意:
-
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
-
错误示例:
// stus为数组 stus.unshift("小刘"); this.setState({ stus }); const obj = this.state; obj.carName = "迈巴赫"; console.log(obj === this.state); // true,判断时认为数据未变化,不更新页面 this.setState(obj);
-
-
不要直接修改state数据, 而是要产生新数据
-
-
项目中一般使用PureComponent来优化
-
七、render props
采用 render props 技术,我们可以像组件内部动态传入带有内容的结构,和Vue插槽类似
1. 组件标签内填入内容时
-
当我们在一个组件标签中填写内容时,这个内容会被定义为 children props,我们可以通过
this.props.children
来获取 -
如:
<A>hello</A>
-
这个 hello 我们就可以通过 children 来获取
2. 如何向组件内部动态传入带内容的结构(标签)?
- Vue中:
- 使用slot技术, 也就是通过组件标签体传入结构
<AA><BB/></AA>
- 使用slot技术, 也就是通过组件标签体传入结构
- React中:
- 使用
children props
: 通过组件标签体传入结构 - 使用
render props
: 通过组件标签属性传入结构, 一般用render函数属性
- 使用
3. children props
-
在Parent组件中
<A> <B>xxxx</B> </A>
-
A组件中使用B组件,AB组件为父子组件关系
{this.props.children}
-
问题: 如果B组件需要A组件内的数据 ==> 做不到
4. render props
-
在Parent组件中
<A render={(name) => <B name={name} />} />
-
A组件:
{this.props.render(name)}
-
C组件: 读取A组件传入的数据显示
{this.props.name}
八、错误边界
1. 理解:
错误边界:用来捕获后代组件错误,渲染出备用页面
2. 特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
3. 使用方式:
getDerivedStateFromError
配合componentDidCatch
// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}
九、组件通信方式总结
1. 方式:
-
props:
-
children props
-
render props
-
-
消息订阅-发布:pubs-sub、event等等
-
集中式管理:redux、dva等等
-
Context:生产者-消费者模式
2. 选择方式
- 父子组件:props
- 兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)
本文来自博客园,作者:你就是星光,转载请注明原文链接:https://www.cnblogs.com/xzqyl/p/17045384.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具