第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. 总结
二、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()
componentWillUnmount()
-
React.useEffect说明
- 第一个参数是一个回调函数,第二个参数是一个数组
- 第二个参数不写:所有state更新时,执行回调函数
- 添加第二个参数(数组):数组中对应的state或prop更新时,执行回调函数,和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
很类似。
四、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个问题
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:
七、render props
采用 render props 技术,我们可以像组件内部动态传入带有内容的结构,和Vue插槽类似
1. 组件标签内填入内容时
2. 如何向组件内部动态传入带内容的结构(标签)?
- Vue中:
- 使用slot技术, 也就是通过组件标签体传入结构
<AA><BB/></AA>
- React中:
- 使用
children props
: 通过组件标签体传入结构
- 使用
render props
: 通过组件标签属性传入结构, 一般用render函数属性
3. children props
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. 方式:
2. 选择方式
- 父子组件:props
- 兄弟组件(非嵌套组件):消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)