React 扩展
- [一、 setState](#一、 setState)
-
- [setState 更新状态的 2 种写法](#setState 更新状态的 2 种写法)
- 二、lazyLoad
-
- [路由组件的 lazyLoad](#路由组件的 lazyLoad)
- 三、Hooks
-
- [1、React Hook/Hooks 是什么?](#1、React Hook/Hooks 是什么?)
- [2、三个常用的 Hook](#2、三个常用的 Hook)
- [3、State Hook](#3、State Hook)
- [4、Effect Hook](#4、Effect Hook)
- [5、Ref Hook](#5、Ref Hook)
- 四、Fragment
- 五、Context
- 六、组件优化
- [七、render props](#七、render props)
-
- [1、如何向组件内部动态传入带内容 的结构(标签)?](#1、如何向组件内部动态传入带内容 的结构(标签)?)
- [2、children props](#2、children props)
- [3、render props](#3、render props)
- 4、代码展示
- 八、错误边界
- 九、组件通信方式总结
-
- 1、组件间的关系
- 2、几种通信方式
-
- [2.1 props](#2.1 props)
- [2.2 消息订阅-发布](#2.2 消息订阅-发布)
- [2.3 集中式管理](#2.3 集中式管理)
- [2.4 context](#2.4 context)
- 3、比较好的搭配方式
一、 setState
setState 更新状态的 2 种写法
(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 函数种读取
jsx
import React, { Component } from 'react'
export default class Demo extends Component {
state = {
count: 0,
}
add = () => {
// 对象式的 setState
// const {count} = this.state
// this.setState({count: count+1},()=>{
// console.log(this.state.count)
// })
// console.log("", this.state.count)
// 函数式的 setState
this.setState((state, props) => {
console.log(state, props)
return { count: state.count + 1 }
})
}
render() {
return (
<div>
<h2>当前求和为: {this.state.count}</h2>
<button onClick={this.add}>点我+1</button>
</div>
)
}
}
二、lazyLoad
路由组件的 lazyLoad
javascript
// 1、通过 React 的lazy 函数配合 import() 函数动态加载路由组件===>路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
// 2、通过 <Suspense> 指定在加载得到路由打包文件前显示一个自定义 loading 界面
<Suspense fallback={<h1>loading...</h1>}>
<Route path='/about' component= {About} />
<Route path='/home' component= {Home} />
</Suspense>
完整 index 代码
jsx
import React, { Component, lazy, Suspense } from 'react'
import { BrowserRouter, Route } from 'react-router-dom'
// import Home from "./pages/Home"
// import About from "./pages/About"
import Header from './components/Header'
import MyNavLink from './components/MyNavLink'
import './index.css'
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
export default class Demo extends Component {
render() {
return (
<BrowserRouter>
<div>
<Header />
<div className="row">
<div className="sidebar">
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink>
</div>
<div className="panel">
<Suspense fallback={<h1>loading</h1>}>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
</Suspense>
</div>
</div>
</div>
</BrowserRouter>
)
}
}
三、Hooks
1、React Hook/Hooks 是什么?
(1)Hook 是 React16.8.0 版本增加的新特性/新语法
(2)可以让你在函数组件中使用 state 以及其他的 React 特性
2、三个常用的 Hook
(1)State Hook: React.useState()
(2)Effect Hook:React.useEffect()
(3)Ref Hook: React.useRef()
3、State Hook
(1)State Hook 让函数组件也可以有 state 状态,并进行状态数据的读写操作
(2)语法:const [xxx, setXxx] = React.useState(initValue)
(3)useState() 说明:
参数:第一次初始化指定的值在内部作缓存
返回值:包含 2 个元素的数组,第 1 个为内部当前状态值,第 2 个为更新状态值得函数
(4)setXxx() 2 种写法:
setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原理啊的状态值
setXxx(value=>newValue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
jsx
import React from 'react'
// 函数式组件
function Demo() {
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('Tom')
console.log(count, setCount)
function add() {
console.log('点击了加号')
// setCount(count+1) // 第一种写法
setCount((count) => {
return count + 1
})
}
function changeName() {
setName('Jack')
}
return (
<div>
<h2>当前求和为: {count}</h2>
<h2>我的名字是: {name}</h2>
<button onClick={add}>点我+1</button>
<button onClick={changeName}>点我改名字</button>
</div>
)
}
4、Effect Hook
(1)Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2)React 中的副作用操作:
发 ajax 请求数据获取
设置订阅/启动定时器
手动更改真实 DOM
(3)语法和说明:
useEffect(()=>{
// 在此可以执行任何带副作用的操作
return ()=>{// 在组件卸载前执行
//在此做一些收尾工作,比如清楚定时器/取消订阅等
}
}, [stateValue])// 如果指定是 [],回调函数只会在第一次 render() 后执行
(4)可以把 useEffect Hook 看做如下三个钩子函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
5、Ref Hook
(1)Ref Hook 可以在函数组件中存储/查找组件内的标签或任意其他数据
(2)语法:const refContainer = useref()
(3)作用:保存标签对象,功能与 React.createRef() 一样
四、Fragment
Fragment 的层级最终会被丢弃,不会被渲染为真实 dom
使用
jsx
<Fragment></Fragment> // 可以被忽略,但是允许写key属性(并且只能有这个属性)
<></>// 可以被忽略,但是不可以写任何属性
作用
可以不用必须有一个真实的 DOM 根标签了
五、Context
1、理解
一种组件间通信方式,常用于【祖组件】和【后代组件】间通信
2、使用
jsx
// 1)创建 Context 容器对象:
const XxxContext = React.createContext()
// 2)渲染子组件时,外面包裹 XxxContext.Provider, 通过 value 属性给后代组件传递数据(value 不可以改成其他名字,只能使用 value):
<XxxContext.Provider value={数据}> 子组件 </XxxContext.Provider>
// 3)后代组件读取数据
// 3.1)第一种方式:仅适用于类组件
static contextType = xxxContext // 声明接收 context
this.context // 读取context 中的 value 数据
// 3.2)第二种方式:函数组件与类组件都可以
<XxxContext.Consumer>
{
value => ( // value 就是 context 中的 value 数据
要显示的内容
)
}
</XxxContext.Consumer>
3、注意
在应用开发中一般不用 Context,一般都用它封装 React 插件
4、代码
jsx
import React, { Component } from 'react'
import './index.css'
const UsernameContext = React.createContext()
export default class Demo extends Component {
state = {
username: 'Tom',
}
render() {
return (
<div className="grandfather">
<h1>我是Demo组件</h1>
<h4>我的用户名是:{this.state.username}</h4>
<UsernameContext.Provider value={this.state}>
<A />
</UsernameContext.Provider>
</div>
)
}
}
class A extends Component {
render() {
return (
<div className="father">
<h3>我是 A 组件</h3>
<h4>我从 Demo 组件接收到的用户名:???</h4>
<B />
</div>
)
}
}
class B extends Component {
// static contextType = UsernameContext
// render() {
// let {username} = this.context
// console.log(this, this.context)
// return (
// <div className='child'>
// <h3>我是 B 组件</h3>
// <h4>我从 Demo 组件接收到的用户名:{username}</h4>
// </div>
// )
// }
render() {
return (
<div className="child">
<h3>我是 B 组件</h3>
<h4>
我从 Demo 组件接收到的用户名:
<UsernameContext.Consumer>
{(value) => value.username}
</UsernameContext.Consumer>
</h4>
</div>
)
}
}
// 如果是函数式组件
// function B() {
// return (
// <div className="child">
// <h3>我是 B 组件</h3>
// <h4>
// 我从 Demo 组件接收到的用户名:
// <UsernameContext.Consumer>
// {(value) => `${value.username},年龄${value.age}`}
// </UsernameContext.Consumer>
// </h4>
// </div>
// )
// }
六、组件优化
1、Component 的 2 个问题
1、只要执行 setState() ,即使不改变状态数据,组件也会重新 render()
2、只当前组件重新 render(),就会自动重新 render 子组件===》效率低
2、效率高的做法
只有当前组件 state 或 props 数据发生改变时才重新 render()
3、原因
Component 中的 shouldComponentUpdate() 总是返回 true
4、解决
方法1:
重写 shouldComponentUpdate() 方法
比较新旧 state 和 props 数据,如果有变化才返回true,如果没有返回 false
方法2:
使用 PureComponent
PureComponent重写 shouldComponentUpdate() 方法, state 和 props 数据,如果有变化才返回true,如果没有返回
注意:
只是进行 state 和 props 数据的浅比较,如果只是数据对象内部数据变了,返回false
不要直接修改 state 数据(push、unshift等方法都是直接修改原数组的),而是要产生新数据
项目中一般使用 PureComponent 来优化
4.1 自己重写 "阀门"
jsx
import React, { Component } from 'react'
import './index.css'
export default class Parent extends Component {
state = {
carname: '奔驰c63',
}
changCarname = () => {
this.setState({ carname: '迈巴赫' })
}
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, this.props) // 下一个props 和 当前 props
console.log(nextState, this.state) // 下一个 state 和当前 state
return nextState.carname !== this.state.carname
}
render() {
console.log('Parent--render')
let { carname } = this.state
return (
<div className="parent">
<h3>我是 Parent 组件</h3>
<span>我的车名字是:{carname}</span>
<button onClick={this.changCarname}>点我换车</button>
<Child />
{/* carname={carname} */}
</div>
)
}
}
class Child extends Component {
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, this.props) // 下一个props 和 当前 props
console.log(nextState, this.state) // 下一个 state 和当前 state
return nextProps.carname !== this.props.carname
}
render() {
console.log('Child--render')
return (
<div className="child">
我是 Child 组件,
{/* <span>我接收到的车名称是:{this.props.carname}</span> */}
</div>
)
}
}
4.2 使用 PureComponent 优化
jsx
import React, { PureComponent } from 'react'
import './index.css'
export default class Parent extends PureComponent {
state = {
carname: '奔驰c63',
}
changCarname = () => {
this.setState({ carname: '迈巴赫' })
}
render() {
console.log('Parent--render')
let { carname } = this.state
return (
<div className="parent">
<h3>我是 Parent 组件</h3>
<span>我的车名字是:{carname}</span>
<button onClick={this.changCarname}>点我换车</button>
<Child />
{/* carname={carname} */}
</div>
)
}
}
class Child extends PureComponent {
render() {
console.log('Child--render')
return (
<div className="child">
我是 Child 组件,
{/* <span>我接收到的车名称是:{this.props.carname}</span> */}
</div>
)
}
}
七、render props
1、如何向组件内部动态传入带内容 的结构(标签)?
Vue 中:
使用 slot 技术,也就是通过组件标签体传入结构 <A><B/></A>
React 中:
使用 children props:通过组件标签体传入结构
使用 render props:通过组件标签属性传入结构,而且可以携带数据,一般用 render 函数属性
2、children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题:如果B组件需要A组件内的数据,===》做不到
3、render props
<A render={(data)=><C data={data}/>}></A>
A 组件:{this.props.rener(内部 state 数据)}
C 组件:读取A组件传入的数据显示 {this.props.data}
4、代码展示
jsx
import React, { Component } from 'react'
import './index.css'
export default class Parent extends Component {
render() {
return (
<div className="parent">
<h3>我是Parent 组件</h3>
<A render={(name) => <B name={name} />}></A>
</div>
)
}
}
class A extends Component {
state = {
name: '里斯',
}
render() {
let { name } = this.state
return (
<div className="child">
<h3>我是 A 组件</h3>
{/* <B /> */}
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
console.log(this.props, 'B')
return (
<div className="child1">
<h3>我是 B 组件</h3>
</div>
)
}
}
八、错误边界
1、理解:
错误边界 Error boundary :用来捕获后代组件组件错误,渲染出备用页面
2、特点
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。
使用方式
getDerivedStateFromError 配合 componentDidCatch
javascript
// 生命周期函数,一旦后代组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error)
// 在render 之前触发
// 返回新的 state
return {
hasErro: true
}
}
componentDidCatch(error, info) {
// 统计页面的错误,发送请求发送到后台后
console.log(error,info)
}
jsx
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError: '', // 用于标识子组件是否产生错误
}
// 当 Parent 的子组件出现任何的报错时,都会调用这个方法,并且携带错误信息
// 适用生产环境,开发环境只能用一下
static getDerivedStateFromError(error) {
console.log(error)
return {
hasError: error,
}
}
// 属于生命周期钩子,子组件引发了问题就能够触发
componentDidCatch() {
console.log('统计错误次数,反馈给服务器,用于通知编码人员进行 bug 的解决')
}
render() {
return (
<div>
<h1>我是 Parent 组件</h1>
{this.state.hasError ? (
<h2>当前网络不稳定,请稍后再尝试</h2>
) : (
<Child />
)}
</div>
)
}
}
九、组件通信方式总结
1、组件间的关系
父子组件
兄弟组件(非嵌套组件)
祖孙组件(跨级组件)
2、几种通信方式
2.1 props
(1)children props
(2)render props
2.2 消息订阅-发布
pubs-sub,event 等等
2.3 集中式管理
redux,dva 等等
2.4 context
生产者-消费者模式
3、比较好的搭配方式
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管、conText(开发用的少,封装插件用的多)