1. setState
setState 的两种写法
(1). setState(stateChange,[callback]) ---- 对象式的setState
1. stateChange 为状态改变的对象 `{}`
2. callback 是可选参数,会在状态改变以及render函数调用完毕之后执行.
(2). setState(updater,[callback]) ---- 函数式的setState
1. updater 为返回 stateChange 的函数 .
2. updater 可以接受两参数 prevState , props
3. callback 是可选参数,会在状态改变以及render函数调用完毕之后执行.
总结:
1. 对象式setState就是函数式的语法糖.(在内部会定义一个函数,返回stateChange对象)
2. 使用原则:
1. 如果修改的状态不依赖原来的状态,使用对象式setState
2. 如果修改的状态依赖于原状态,使用函数式setState
3. 如果需要在setState之后,拿到最新的状态,则需要使用可选参数 callback.
代码例子:
import React, { Component } from 'react'
export default class Demo extends Component {
state = {
count: 1
}
add = () => {
//#region 对象式的setState
// 在原来的对象上修改,也可以修改状态
// this.state.count++
// this.setState(this.state)
// 第一种setState的方法
// this.setState({ count: this.state.count + 1 })
// console.log(this.state.count) // 1 setState 修改也是异步的,所以这里返回的是1
// 如果你需要使用最新的state状态,在setState的第二个回调函数里就可以拿到(在state和界面更新之后调用)
// this.setState({ count: this.state.count + 1 }, () => {
// console.log(this.state.count) // 2
// })
// this.setState({ count: this.state.count + 1 })
// setTimeout(() => {
// console.log(this.state.count);
// }, 0);
//#endregion
//#region 函数式的setState
console.log('setState');
this.setState((prevState, props) => {
console.log(props);
return { count: prevState.count + 1 }
}, () => {
// 在状态更新之后执行render之后执行
console.log('callback');
console.log(this.state.count)
})
//#endregion
}
render() {
console.log('render');
const { count } = this.state
return (
当前计数为: {count}
)
}
}
2. 路由组件的 lazyLoad
路由组件的lazyLoad
// 1. 通过React的lazy函数配合 import()函数动态加载路由组件 ====> 路由组件的代码会单独打包
import { lazy } from 'react'
const Login = lazy(()=>import('@/pages/login'))
// 2. 通过 指定在加到得到的路由打包组件加载完毕之后,自由的显示一个Loading组件
render () {
return (
}>
)
}
3. Hooks
React Hook是什么?
- Hook 是 react 16.8.0 版本中新增加的新特性/新语法.
- 可以让你在函数式组件中,使用class组件的其他特性(比如,state,生命周期等)
三个常用的hook
- React.useState()
- React.useEffect()
- React.useRef()
React.useState
- React.useState 可以让函数式组件,也可以用于类似于class组件的 state 状态.
- 语法 const [xxx,setXXX] = React.useState(initValue)
- React.useState参数和返回值说明
- 参数 initValue 就是当前状态的初始值.
- 返回值:
- 返回值是一个数组 [xxx,setXXX]
- 第一个xxx,就是state的值.
- 第二个参数是用于修改xxx为新状态的函数.
- setValue的两种用户
- setXXX(newVal):参数为非函数,直接用新的状态覆盖原来旧的状态.
- setXXX(prevState=>newState): 参数为函数,函数接受上一次的状态,在此函数内部返回一个新的状态覆盖原来旧的状态.
演示代码:
import React, { useState } from 'react';
function Demo(props) {
// 让函数式组件可以拥有自己的状态,第一次调用此函数组件是,react内部会将count缓存,后期再次调用此组件时,用的是缓存的值.
const [count, setCount] = useState(0)
const [name, setName] = useState('jack')
// 点我加1
const add = () => {
// setCount(count + 1)
setCount((count) => count + 1)
// console.log('newCount:', count); // 同步无法拿到最新的count
}
const change = () => {
setName(name => name += "gqs")
}
return (
当前求和为:{count}
当前的名字:{name}
);
}
export default Demo;
React.useEffect
- React.useEffect 可以让你在函数式组件中,模拟class组件的生命周期钩子函数.(所谓的副作用操作)
- React中有哪些副作用操作?
- 异步请求
- 设置发布订阅/启动定时器
- 手动更改真实的DOM.
- 语法和说明
// 等价于 class 组件的 componentDidUpdate 钩子 useEffect(()=>{ console.log('当任意state发生改变时,都会触发-componentDidUpdate') // 监听所有的属性发生改变时,都会出发此函数,等价于 componentDidUpdate }) // 等价于 class 组件的 componentDidMount 钩子 useEffect(()=>{ console.log('componentDidMount') },[]) // 参数传空数组,不监听任何state的change,等价于 componentDidMount // 等价于 class 组件的 componentDidUpdate ,仅针对监听的state useEffect(()=>{ console.log('当count的state放生改变时触发 - componentDidUpdate') },[count]) // 等价于 class 组件的 componentWillUnmount useEffect(()=>{ return () => { console.log('componentWillUnmount') } },[])
- 可以将
useEffect
加上参数组合的方式,来实现以下三种生命周期钩子效果.-
componentDidUpdate
- 监听所有属性,或者某些属性 -
componentWillUnmount
- 当函数式组件销毁时 -
componentDidMount
- 不监听任意属性.
-
React.useRef
- React.useRef 可以在函数式组件中存储/查找组件内标签或任意其他数据
- 语法: const ref = React.useRef(), 然后再你需要引用的组件或者dom元素上使用 ref={ref} 即可.
- 作用: 用于保存标签对象,功能于 React.createRef 一致.
- useRef 既可以引用DOM 也可以引用组件实例.
import React, { useRef } from 'react';
import Test from './test';
function Demo(props) {
// 声明一个input的引用
const inputRef = useRef()
// 能引用一个组件吗? -> 结论,可以
const testRef = useRef()
const show = () => {
// alert(document.getElementById('abc').value)
alert(inputRef.current.value)
}
const test = () => {
console.log(testRef.current)
testRef.current.show()
}
return (
{/* 这里是引用一个组件 */}
);
}
export default Demo;
Fragment
<>>
作用:
可以不必拥有一个真实的dom.用上述两种标签占位!
createContext
一种祖孙组件间的通信方式,类似于Vue的 provider/inject .
使用方式:
1. 创建context容器对象
const Context = React.createContext()
2. 渲染子组件时,外面包裹 , 并通过value属性,将需要传递给后代组件.
3. Other 组件以及后面的所有组件,就都可以通过某些设置,拿到value的值.
4. 后代组件读取数据.
1. class 组件 (仅用于class组件)
static contextType = Context
this.context = 就能拿到value的值.
2. 函数式组件 (函数式组件和类式组件都可以)
{
(value) => {
// value 就是context提供的值.
return {value.name} - {value.age}
}
}
演示代码如下:
import React, { Component } from 'react'
import './index.scss'
// 创建context上下文组件
const NameContext = React.createContext()
export default class A extends Component {
state = { name: 'tom' }
render() {
return (
我是A组件
我的用户名是:{this.state.name}
{/* 第一步 */}
)
}
}
class B extends Component {
// 第二步,声明接受contextType
static contextType = NameContext
render() {
return (
我是B组件
{/* 父子组件间可以使用props传递 */}
{/* 我从A组件拿到的用户名是:{this.props.name}
*/}
我从A组件拿到的用户名是:{this.context.name}
)
}
}
class C extends Component {
// 第二步,声明接受contextType
static contextType = NameContext
render() {
// console.log(this);
return (
我是C组件
我从A组件拿到的用户名是:{this.context.name}
)
}
}
// 函数式组件
function D() {
return (
我是D组件
我从A组件拿到的用户名是:
{
value => {
return `${value.name}`
}
}
)
}
组件渲染优化
React.Component 的 2 个问题
- 只要执行了setState,发生了状态改变,不论state状态是否在模板上显示,都会重新调用 render 函数.但组件效率低.
- 如果父组件重新render了,那么子组件也会被render,多组件效率低下.
效率高的做法
只有当依赖的state或者props,在视图中显示时,才调用render函数.
问题核心:
React 中,你只要调用了 setState 就会重新render,因为 shouldComponentUpdate() 钩子函数总是返回true.
解决办法:
- 重写
shouldComponentUpdate
,比较state
和props
来决定是否调用render
渲染函数.- 使用
PureComponent
PureComponent
重写了shouldComponentUpdate
, 只有当state
或者props
有变化时才返回为true
注意: 只是进行state
和props
的浅层比较. 如果只是对象内部的数据发生了变化,直接返回false.你必须在setState里传递一个新的对象
项目中,一般使用PureComponent
用来进行渲染性能优化.
演示代码:
import React, { Component } from 'react'
// // 测试代码一
// export default class Demo extends Component {
// update = () => {
// this.setState({})
// }
// render() {
// console.log('React render 很蠢,只要你调用了setState,而不管状态是否依赖在视图上,都会执行render函数!原因是 componentShouldUpdate 默认总是返回true')
// return (
//
// React render 很蠢,只要你调用了setState,而不管状态是否依赖在视图上,都会执行render函数!原因是 componentShouldUpdate 默认总是返回true
//
//
// )
// }
// }
// 测试代码二,自己来优化,只有当props和state发生改变时,才更新视图,重新调用render 函数
// export default class Demo extends Component {
// state = {
// username: '张三'
// }
// changeName = () => {
// this.setState({ username: '李四' })
// // this.setState({}) 如果传空对象,那么 nextState 和 this.state 时同一个对象.
// // username:undefined
// }
// /**
// *
// * @param {*} newProps 更新后的 props
// * @param {*} newState 更新后的 state
// */
// shouldComponentUpdate(nextProps, nextState) {
// // console.log(this.state, nextState);
// const oldStateStr = JSON.stringify(this.state)
// const newStateStr = JSON.stringify(nextState)
// const oldPropsStr = JSON.stringify(this.props)
// const newPropsStr = JSON.stringify(nextProps)
// // 当新旧props和state发生变化时,才需要render.
// return oldStateStr !== newStateStr || oldPropsStr !== newPropsStr
// }
// render() {
// console.log(`render`);
// const { username } = this.state
// return (
//
// 用户的名字是:{username}
//
// {/* 没依赖父组件的数据,没接受props, 就没有必要render */}
//
//
// )
// }
// }
// class Child extends React.Component {
// // 由于默认情况下,父组件render 会导致子组件render.所以,对于某些静态组件,可以直接返回false,不需要render
// shouldComponentUpdate(nextProps, nextState) {
// // 对于纯展示的静态组件,可以直接返回false
// return false
// }
// render() {
// console.log('child-render');
// return (
// 纯静态组件,无需调用render
// )
// }
// }
// 测试代码三,使用 PureComponent
// PureComponent 自动帮我们实现了 shouldComponentUpdate 函数,但是只有一层比较.
export default class Demo extends React.PureComponent {
state = {
name: '李四',
age: 22, // 浅层
info: {
address: '湖北', // 深层
zicode: 43333
}
}
changeName = () => {
this.setState({
name: '张三'
})
// const obj = this.state
// obj.name = '李四'
// this.setState(obj) // 注意,当你继承了PureComponent,那么 setState 里必须传递一个新的对象!否则,setState 函数内部拒绝执行之后的代码
/**
* setState (stateChange) {
* if (orginState === stateChange) return
* }
*
*/
}
changeAge = () => {
this.setState({
age: 33
})
}
changeAddress = () => {
// 对于大于一层属性的修改,PureComponent 不会进行对比前后是否相等,而是直接调用render
this.setState({
info: {
address: '武汉'
}
})
}
changeZiCode = () => {
// 对于大于一层属性的修改,PureComponent 不会进行对比前后是否相等,而是直接调用render
this.setState({
info: {
zicode: 123456
}
})
}
render() {
console.log('render');
const { name, age, info: { address, zicode } } = this.state
return (
name(浅层属性):{name}
age:(浅层属性):{age}
address:(深层属性):{address}
zicode:(深层属性):{zicode}
)
}
}
使用 PureComponent
的注意点:
必须给
setState
传递一个新的对象,否则setState
不做任何操作!
// const obj = this.state
// obj.name = '李四'
// this.setState(obj) // 注意,当你继承了PureComponent,那么 setState 里必须传递一个新的对象!否则,setState 函数内部拒绝执行之后的代码
/**
* setState (stateChange) {
* if (orginState === stateChange) return
* }
*
*/
render props
如何在 React
里实现类似 Vue
插槽的技术?
- 使用 render props
请查看代码
import React, { PureComponent } from 'react'
import './index.scss'
export default class Demo extends PureComponent {
render() {
return (
我是最外层组件
{/* 给Child组件传递了一个render属性,是一个函数,函数返回一个组件实例 */}
( )} />
)
}
}
class Child extends PureComponent {
state = {
name: 'Hello World In Child Component'
}
render() {
return (
我是Child组件,我的state值是:{this.state.name}
{/* 拿到props里的render,然后去调用这个实例 第一种实现插槽的方式 */}
{this.props.render(this.state.name)}
);
}
}
function Demo2(props) {
return (
我是Demo2函数式组件,我由Child组件渲染.
收到来自Child组件的数据:{props.name}
)
}
- 使用 this.props.children
查看代码:
class Child2 extends React.PureComponent {
render() {
return (
我是Child2组件
{/* 第二种实现插槽的方式 */}
{this.props.children}
)
}
}
function Demo2(props) {
console.log(props);
return (
我是Demo2函数式组件,我由Child组件渲染.
收到来自Child组件的数据:{props.name}
)
}
错误边界
理解:
错误便捷(
Error boundary
);用来解决后代组件的错误,渲染出备用页面,不至于内部的某个组件发生错误,导致整个程序全部崩溃!
特点:
只能捕获后代组件[生命周期里]发生的错误,不能捕获自身由于用于操作,定时器,以及合成事件里的错误! 你可以理解为,只能捕获组件第一次初始化渲染时候发生的错误!
演示代码:
import React, { PureComponent } from 'react'
import './index.scss'
export default class Parent extends PureComponent {
state = {
hasError: '' // 用于标识子组件是否发生错误?
}
// 当 parent的所有后代出现error时,会出发此函数的调用,并携带error参数
static getDerivedStateFromError(error) {
console.log(error)
return {
hasError: error
}
}
componentDidCatch(error) {
// 可以收集到错误,并发送给后台统计.
console.log("&&&", error)
}
render() {
return (
我是Parent组件组件
{this.state.hasError ? 当前网络不稳定,请稍后再试....
: }
)
}
}
class Child extends PureComponent {
state = {
users: undefined
}
happen = () => {
// 这种错误无法捕获,只有在生命周期里的错误才能捕获!
throw new Error('在child组件里发生了一个错误!')
}
render() {
return (
我是Child组件
{this.state.users.map(user => (- {user.name}
))}
)
}
}
组件间通信的方式
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套关系)
- 祖孙组件
集中通信方式:
- props
- 父 -> 子 props.everyThing 子 -> 父 props.function
- render props 插槽.
- 消息发布订阅
- pubsub / eventBus 等.
- 状态集中式管理
- redux / dva 等等
- context
- 类似 Vue 的 Provider / inject