Skip to content

关于 React 的几个常用技巧

一、 setState

1. setState更新状态的2种写法

  1. setState(stateChange, [callback])------对象式的 setState
    • 1.stateChange 为状态改变对象(该对象可以体现出状态的更改)
    • 2.callback 是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
  2. setState(updater, [callback])------函数式的 setState
    • 1.updater 为返回 stateChange 对象的函数,可以接收到 state 和 props
    • 2.callback 是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

2. 代码示例

import React, { Component } from 'react'
export default class SetState extends Component {
state = { count: 0 }
increment = () => {
// !组件的状态更新是异步的
// * 对象形式的 setState
// const { count } = this.state
// // setState() 第二个参数为render执行之后的回调
// this.setState({ count: count + 1 }, () => {
// console.log(this.state.count);
// })
// * 函数式的 setState
// setState(updateFn(state,props), cb) , 第二个参数cb为render执行之后的回调
this.setState((state, props) => {
// state 就是维护的状态,props为组件接收的属性值
// console.log(state, props);
return { count: state.count+1 }
},)
}
render() {
return (
<div>
<h2>当前求和为:{this.state.count}</h2>
<button onClick={this.increment}>点击+1</button>
</div>
)
}
}

3. 总结

  1. 对象式的 setState 是函数式的 setState 的简写方式(语法糖)
  2. 使用原则:
    1. 如果新状态不依赖于原状态 ===> 使用对象方式
    2. 如果新状态依赖于原状态 ===> 使用函数方式
    3. 如果需要在 setState() 执行后获取最新的状态数据, 要在第二个 callback 函数中读取

二、lazyLoad 组件懒加载

1. 路由组件的lazyLoad

import React, { Component, lazy, Suspense } from 'react'
import { NavLink, Route } from 'react-router-dom'
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))
export default class Lazyload extends Component {
render() {
return (
<div>
{/* 编写路由链接 */}
<NavLink to='/home'>home</NavLink>&nbsp;&nbsp;
<NavLink to='/about'>about</NavLink>
{/* 注册路由 */}
{/*2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面*/}
{/* fallback 表示在跳转的过程中,展示的东西,可以是标签,或者一个组件(必须是正常引入的组件) */}
<Suspense fallback={<h1>loading...</h1>}>
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
</Suspense>
</div>
)
}
}

2. 总结

  1. 路由懒加载必须借助 react 中的 lazy 函数和 Suspense 组件
  2. 路由的导入方式:const Home = lazy(() => import('./Home'))
  3. 注册路由要用 <Suspense></Suspense> 包裹

三、Hooks

1. React Hook/Hooks是什么?

  1. Hook是React 16.8.0版本增加的新特性/新语法
  2. 可以让你在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

  1. State Hook: React.useState()
  2. Effect Hook: React.useEffect()
  3. Ref Hook: React.useRef()
(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): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

代码示例

import React from 'react'
export default function Hook() {
// count 为初始值,setCount 为改变初始值的函数
// * 1. 为每一个状态单独开一个 useState()
// (官方建议为每一个状态单独进行管理,不推荐使用对象形式)
const [count, setCount] = React.useState(0)
// 点击 + 1
const increment = () => {
// 直接传入参数调用,就可以改变初始值
setCount(count + 1) // 第一种方式
// setCount((count) => count + 1) // 第二种方式
}
return (
<div>
<h2>当前求和为:{ count }</h2>
<button onClick={increment}>点击+1</button>
</div>
)
}

4. Effect Hook

  1. Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)

  2. React中的副作用操作:发ajax/axios请求数据获取、设置订阅 / 启动定时器、手动更改真实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() 一样

代码示例

import React from 'react'
export default function Hook() {
const [input, setInput] = React.useState('')
// * useRef(),创建一个容器,与类式组件中的createRef相同的用法
const myRef = React.useRef()
// 展示输入的数据
function show(e) {
setInput(e.target.value)
}
return (
<div>
<h2>你的输入:{input }</h2>
{/* ref 的值即为上面创建的 myRef 容器*/}
<input type="text" ref={myRef} onInput={show} placeholder='请输入'/>
</div>
)
}

四、Fragment

1. 作用

可以不用必须有一个真实的DOM根标签了

2. 代码示例

import React, { Component, Fragment } from 'react'
export default class FragmentDemo extends Component {
render() {
return (
// 作用:可以不用必须有一个真实的DOM根标签了
// <></> 空标签也可以
// 如果要遍历,就使用Fragment,因为可以添加key值
<Fragment key={1}>
<input type="text" />
<input type="text" />
</Fragment>
)
}
}

五、Context 上下文对象

1.理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

2. 使用

  1. 创建Context容器对象:const XxxContext = React.createContext()

  2. 渲染子组件时,外面包裹xxxContext.Provider, 通过 value 属性给后代组件传递数据:

    <xxxContext.Provider value={数据}>
    子组件
    </xxxContext.Provider>
  3. 后代组件读取数据:

    //第一种方式:仅适用于类组件
    static contextType = xxxContext // 声明接收context
    this.context // 读取context中的value数据
    //第二种方式: 函数组件与类组件都可以
    <xxxContext.Consumer>
    {
    value => ( // value就是context中的value数据
    要显示的内容
    )
    }
    </xxxContext.Consumer>

3. 代码示例

import React, { Component } from 'react'
import './index.css'
// 创建 Context 容器组件
const MyContext = React.createContext()
// 父组件
export default class A extends Component {
state = {
username: 'coderbin',
age: 21
}
render() {
const { username, age} = this.state
return (
<div className='parent'>
<h2>我是 A 组件</h2>
<h3>我的用户名是:{this.state.username}</h3>
{/* 注意:这里必须通过 value属性向后代传递数据 */}
<MyContext.Provider value={{username, age}}>
<B />
</MyContext.Provider>
</div>
)
}
}
// 子组件
class B extends Component{
render() {
return (
<div className='child'>
<h2>我是 B 组件</h2>
<C />
</div>
)
}
}
// 孙组件
// 类式组件接收数据的方法
// class C extends Component{
// // 声明接收 context
// static contextType = MyContext
// render() {
// const {username, age} = this.context
// return (
// <div className='grand'>
// <h2>我是 C 组件</h2>
// <h3>我是从A组件接收到的用户名:{username},年龄是:{ age }</h3>
// </div>
// )
// }
// }
// 函数式组件接收数据的方法
// 使用 <MyContext.Consumer> 包裹,在里面接收展示数据
function C() {
return (
<div className='grand'>
<h2>我是 C 组件</h2>
<h3>我是从A组件接收到的用户名:
<MyContext.Consumer>
{value => `${value.username},年龄是:${value.age}`}
</MyContext.Consumer>
</h3>
</div>
)
}

4. 注意

在应用开发中一般不用context, 一般都用它的封装react插件

六、组件优化

1. Component的2个问题

  1. 只要执行 setState(),即使不改变状态数据, 组件也会重新 render() ==> 效率低
  2. 只要当前组件重新 render(), 就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

2. 效率高的做法

  • 只有当组件的 state 或 props 数据发生改变时才重新 render()

3. 原因

  • Component 中的 shouldComponentUpdate() 总是返回 true

4. 解决

  1. 办法1:
    • 重写 shouldComponentUpdate() 方法
    • 比较新旧 state 或 props 数据, 如果有变化才返回 true, 如果没有返回 false
  2. 办法2:
    • 使用 PureComponent
    • PureComponent 重写了 shouldComponentUpdate(), 只有 state 或 props 数据有变化才返回 true
  3. 注意
    • 只是进行 state 和 props 数据的浅比较, 如果只是数据对象内部数据变了, 返回 false
    • 不要直接修改 state 数据, 而是要产生新数据
    • 项目中一般使用 PureComponent 来优化

5. 代码示例

import React, { PureComponent } from 'react'
import './index.css'
// PureComponent 重写了 shouldComponentUpdate(),
// 只有 state 或 props数据有变化才返回true
// 注意:
// 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
// 不要直接修改state数据, 而是要产生新数据
export default class Parent extends PureComponent {
state = {
carName: '奔驰'
}
changeCar = () => {
this.setState({
carName: '法拉利'
})
}
// shouldComponentUpdate(nextProps, nextState) {
// // 当前值
// console.log(this.props, this.state);
// // 接下来要变化的目标 Props,目标 State
// console.log(nextProps, nextState);
// return true
// }
render() {
console.log('Parent -- render');
const { carName } = this.state
return (
<div className='parent'>
<h3>我是 Parent 组件</h3>
<span>我的车的名字是:{carName}</span><br />
<button onClick={this.changeCar}>点击换车</button>
<Child carName={carName} />
</div>
)
}
}
class Child extends PureComponent {
render() {
console.log('Child -- render');
return (
<div className='child'>
<h3>我是 Parent 组件</h3>
<span>我接收到的车是:{ this.props.carName }</span>
</div>
)
}
}

七、render props

1. 如何向组件内部动态传入带内容的结构(标签)?

Vue中:

  • 使用 slot 插槽技术, 也就是通过组件标签体传入结构 <A><B/></A>

React中:

    1. 使用 children props: 通过组件标签体传入结构
    1. 使用 render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

2. children props

  1. 父组件: <A> <B>xxxx</B></A>
  2. 子组件:{this.props.children}

问题:如果 B 组件需要 A 组件内的数据, ==> 做不到

3. render props

  1. 最外层组件:<A render={(data) => <B data={data}></B>}></A>
  2. A组件:{this.props.render(内部state数据)}
  3. B组件:读取A组件传入的数据显示 {this.props.data}

4. 代码示例

import React, { Component } from 'react'
import './index.css'
export default class RenderProps extends Component {
render() {
return (
<div className='parent'>
<h3>我是 Parent 组件</h3>
{/* 1. 给 A 组件一个 render 属性,值为一个函数,里面返回 B 组件 */}
{/* 2. 这个函数可以接受一个来自 A 组件的参数,传递给 B 组件 */}
<A render={(name) => <B name={name} />} />
</div>
)
}
}
class A extends Component {
state = { name: 'coderbin'}
render() {
const { name } = this.state
return (
<div className='a'>
<h3>我是 A 组件</h3>
{/* 1. 接收传过来的 render,直接调用即可渲染 B 组件*/}
{/* 2. 调用的时候可以传递一个数据 */}
{this.props.render(name)}
</div>
)
}
}
class B extends Component {
render() {
console.log('接收到了 A 组件传递的数据:',this.props.name);
return (
<div className='b'>
<h3>我是 B 组件</h3>
</div>
)
}
}

八、错误边界

1. 理解

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

2. 特点

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

3. 使用方式

两个生命周期函数:getDerivedStateFromError 配合 componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
console.log(error);
// 在render之前触发
// 返回新的state
return {
hasError: true,
};
}
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log(error, info);
}

4. 代码示例

Child.jsx

import React, { Component } from 'react'
export default class Child extends Component {
state = {
users:[
{id:'001',name:'tom',age:18},
{id:'002',name:'jack',age:19},
{id:'003',name:'peiqi',age:20},
]
// 测试错误的情况
// users:'abc'
}
render() {
return (
<div>
<h2>我是Child组件</h2>
{
this.state.users.map((userObj)=>{
return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>
})
}
</div>
)
}
}

Parent.jsx

import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = {
hasError:'' //用于标识子组件是否产生错误
}
//当 Parent的子组件出现报错时候,会触发 getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
console.log('@@@',error);
return {hasError:error}
}
// 生命周期函数:组件挂载过程中,如果子组件出了错误,就会调用这个钩子
componentDidCatch(){
console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}

九、组件通信方式总结

1. 组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

2. 几种通信方式:

  1. props:
    • children props
    • render props
  2. 消息订阅-发布:pubs-sub、event等等
  3. 集中式管理:redux、dva等等
  4. conText:生产者-消费者模式

3. 比较好的搭配方式:

  1. 父子组件:props
  2. 兄弟组件:消息订阅-发布、集中式管理
  3. 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

每文一句:立志宜思真品格,读书须尽苦功夫。