09_React 扩展

React 扩展

一、 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(开发用的少,封装插件用的多)

相关推荐
逆旅行天涯26 分钟前
【Threejs】从零开始(六)--GUI调试开发3D效果
前端·javascript·3d
长风清留扬1 小时前
小程序毕业设计-音乐播放器+源码(可播放)下载即用
javascript·小程序·毕业设计·课程设计·毕设·音乐播放器
m0_748247801 小时前
Flutter Intl包使用指南:实现国际化和本地化
前端·javascript·flutter
用户30587584891252 小时前
Connected-react-router核心思路实现
react.js
ZJ_.2 小时前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
joan_852 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特3 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6173 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O5 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.11 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js