12_React扩展(setState的两种写法、lazyLoad、Hooks等)

目录

一、setState的两种写法

第一种 对象式  setState(stateChange, [callback])

第二种 函数式  setState(updater, [callback])

二、lazyLoad懒加载(动态加载路由组件)

三、Hooks(函数式组件)

严格模式下this指向问题

1. useState

2. useEffect

3. useRef

四、Fragment

Fragment编译时把节点丢掉,不在渲染真实DOM,拥有key属性,可以参与遍历

五、Context(createContext、useContext)

5.1 类式组件

1. createContext

5.2 函数式组件

2. useContext

六、组件优化(PureComponent)

extends Component问题

七、children props和render props

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

八、错误边界 

九、组件通信方式

1. 组件间的关系

2. 组件间的通信方式

3.  比较好的搭配方式:


一、setState的两种写法

import React, { Component } from "react"
import "./index.css"
export default class State extends Component {
  state = { count: 0 }
 
  render() {
    return (
      <div>
        <h2>setState的两种写法</h2>
        <p>{this.state.count}</p>
        <button onClick={this.add}>+1</button>
      </div>
    )
  }
}

第一种 对象式  setState(stateChange, [callback])

  1. stateChange: 状态改变对象
  2. callback: 可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
    1. setState本身是同步的方法,但是引起React后续更新状态的动作是异步,所有想要知道当前的state应在callback进行输出

add = () => {
   const { count } = this.state
   this.setState({ count: count + 1 }, () => {
      console.log(this.state.count)
    })
  
}

第二种 函数式  setState(updater, [callback])

  1. updater: 返回stateChange对象的函数,updater可以接收到state和props
  2. callback: 可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用
add = () => {
    this.setState((state) => ({ count: state.count + 1 }))
  }

二、lazyLoad懒加载(动态加载路由组件)

1.通过React的lazy函数配合import()函数动态加载路由组件,路由组件代码会被分开打包

2.注册路由时,通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面,调慢网速查看效果

  • LazyLoad组件
import React, { Component, lazy, Suspense } from "react"
import { Link, Route } from "react-router-dom"
//加载中
import Load from "./Load"
const About = lazy(() => import("./About"))
const Home = lazy(() => import("./Home"))

export default class index extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header">
              <h2>React Router Demo</h2>
            </div>
          </div>
        </div>
        <div className="row">
          <div className="col-xs-2 col-xs-offset-2">
            <div className="list-group">
              <Link
                className="list-group-item"
                to="/about"
              >
                About
              </Link>
              <Link
                className="list-group-item"
                to="/home"
              >
                Home
              </Link>
            </div>
          </div>
          <div className="col-xs-6">
            <div className="panel">
              <div className="panel-body">
                <Suspense fallback={<Load />}>
                  <Route
                    path="/about"
                    component={About}
                  />
                  <Route
                    path="/home"
                    component={Home}
                  />
                </Suspense>
              </div>
            </div>
          </div>
        </div>
      </div>
    )
  }
}

三、Hooks(函数式组件)

  • 原先的函数式组件因为this指向问题,所有只能定义简单的组件。
  • 对象中的this,指向的实例出来的对象,所以类式组件可以定义拥有状态的复杂组件。
  • Hook是React 16.8.0版本增加的新特性/新语法,可以让你在函数式组件中使用 state 以及其他的 React 特性。

严格模式下this指向问题

  1. 普通的this指向,指向的是window
  2. 函数中的this指向,指向的是undefined
  3. 对象中的this,指向的实例出来的对象
  4. 构造函数中的this,指向的是实例出来的对象
  5. 事件中的对象this,指向的是触发该事件的对象
  6. 定时器中的this,指向的是window

1. useState

  • 作用:让函数组件也可以有state状态, 并进行状态数据的读写操作
  • 语法:useState(initialState)
  • 用法:
const [state, setState] = useState(initialState)

案例

import React, { useState, useEffect } from "react"
import "./index.css"

export default function Hook() {
  // 1+n次
  // console.log("$")
  //数组[状态,更新状态打方法]
  const [count, setCount] = useState(0)
  const [name, setName] = useState("tom")
  // console.log(count, add)

  function add() {
    setCount(count + 1)
    // setCount((count) => count + 1)
  }

  function changeName() {
    setName("丽丽")
  }

  return (
    <div>
      <h2>setState的两种写法</h2>
      <h3>我的名字是:{name}</h3>
      <p>{count}</p>
      <button onClick={add}>+1</button>
      <button onClick={changeName}>换名字</button>
    </div>
  )
}

2. useEffect

  • 作用:用于模拟类组件中的生命周期钩子,组件每渲染一次,该函数就自动执行一次
    • 可以看做 react 中
      • componentDidMount
      • componentDidUpdate
      • componentWillUnmount
      • 这三个生命周期函数的组合,也就是在这三个时候会执行
  • 语法:useEffect(setup, dependencies?)
  • setup要执行的函数
    • 副作用操作函数
      • 手动更改真实DOM
      • 设置订阅 / 启动定时器
      •  发ajax请求数据获取
    • 无需清除的副作用操作,直接在useEffect( method )中传入操作函数。取消监听只需要在method里return一个取消监听的函数即可
  • dependencies依赖项数组
    • 每次渲染完DOM执行副作用函数时都会浅比较dependence渲染前后的值是否一致,不一致就执行副作用,反之就不执行
    • 如果该dependence为一个空数组[ ],即没有传入比较变化的变量,则比较结果永远都保持不变,那么副作用逻辑就只能执行一次,在第一次render()后执行
  • 用法:
useEffect(() => {
    //副作用操作
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    //取消监听只需要在method里return一个取消监听的函数即可
    return () => {
      connection.disconnect();
    };
  }, [roomId, serverUrl])

案例

  • 入口文件
export const root = createRoot(document.getElementById("root"))
  • Hook组件 
import React, { useState, useEffect } from "react"
import "./index.css"
import { root } from "../../index"

export default function Hook() {
  const [count, setCount] = useState(0)
 
  useEffect(() => {
    let timer = setInterval(() => {
      setCount((count) => count + 1)
    }, 1000)
    // console.log("#")
    return () => {
      clearInterval(timer)
    }
  }, [])
  function add() {
    setCount(count + 1)
  }

  function unmount() {
    root.unmount()
  }
  return (
    <div>
      <p>{count}</p>
      <button onClick={add}>+1</button>
      <button onClick={unmount}>卸载</button>
    </div>
  )
}

3. useRef

作用:在函数组件中存储、查找组件内的标签或任意其它数据。用于保存标签对象,功能与React.createRef()一样

语法:useRef(initValue)

用法:

const ref = useRef(initialValue)

案例

import React, { useState, useEffect, useRef } from "react"
import "./index.css"
import { root } from "../../index"

export default function Hook() {
  const intervalRef = useRef("hi")

  function show() {
    //提示输入的回调
    alert(intervalRef.current.value)
  }
  return (
    <div>
      <input
        type="text"
        ref={intervalRef}
      />
      <button onClick={show}>展示数据</button>
    </div>
  )
}

四、Fragment

Fragment编译时把节点丢掉,不在渲染真实DOM,拥有key属性,可以参与遍历

补充

<> </>空标签编译时把节点丢掉。不能参与遍历,没有key属性

import React, { Component, Fragment } from "react"

export default class Frag extends Component {
  render() {
    return (
      <Fragment key={1}>
        <input type="text" />
      </Fragment>
    )
  }
}

五、Context(createContext、useContext)

5.1 类式组件

1. createContext

作用:  创建一个context以便组件能够 提供和读取一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

语法:createContext(defaultValue)

用法:

const SomeContext = createContext(defaultValue)

 案例

import React, { Component, createContext } from "react"
import "./index.css"
//创建一个上下文
const MyContext = createContext()
const { Provider } = MyContext
export default class A extends Component {
  state = { username: "Tom", age: 18 }
  render() {
    const { username, age } = this.state
    return (
      <div className="grand">
        <h3>A组件</h3>
        <h4>我的用户名:【{username}】</h4>
        <Provider value={{ username, age }}>
          <B />
        </Provider>
      </div>
    )
  }
}

class B extends Component {
  static contextType = MyContext

  render() {
    const { username, age } = this.context
    return (
      <div className="parent">
        <h3>B组件</h3>
        <h4>
          从A组件接收到的用户名:【{username}】,年龄【{age}】
        </h4>
        <C />
      </div>
    )
  }
}

class C extends Component {
  static contextType = MyContext

  render() {
    const { username } = this.context
    return (
      <div className="child">
        <h3>C组件</h3>
        <h4>从A组件接收到的用户名:【{username}】</h4>
      </div>
    )
  }
}

5.2 函数式组件

2. useContext

作用:可以让你读取和订阅组件中的context

用法:useContext(SomeContext)

const value = useContext(SomeContext)

案例

  • 原始写法
import React, { createContext, Component } from "react"
import "./index.css"
//创建一个上下文
const MyContext2 = createContext()
const { Provider, Consumer } = MyContext2
export default class A extends Component {
  state = { username: "Tom", age: 18 }
  render() {
    const { username, age } = this.state
    return (
      <div className="grand">
        <h3>A组件</h3>
        <h4>我的用户名:【{username}】</h4>
        <Provider value={{ username, age }}>
          <B />
        </Provider>
      </div>
    )
  }
}
function B() {
  //函数式读取上下文
  return (
    <div className="parent">
      <h3>B组件</h3>
      <h4>
        从A组件接收到的用户名:
        <Consumer>
          {(value) => {
            return `【${value.username}】,年龄【${value.age}】`
          }}
        </Consumer>
      </h4>
      <C />
    </div>
  )
}

function C() {
  return (
    <div className="child">
      <h3>C组件</h3>
      <h4>
        从A组件接收到的用户名:
        <Consumer>
          {(value) => {
            return `【${value.username}】`
          }}
        </Consumer>
      </h4>
    </div>
  )
}
  • useContext写法
import React, { createContext, Component, useContext } from "react"
import "./index.css"
//创建一个上下文
const MyContext3 = createContext()
const { Provider } = MyContext3
export default class A extends Component {
  state = { username: "Tom", age: 18 }
  render() {
    const { username, age } = this.state
    return (
      <div className="grand">
        <h3>A组件</h3>
        <h4>我的用户名:【{username}】</h4>
        <Provider value={{ username, age }}>
          <B />
        </Provider>
      </div>
    )
  }
}
function B() {
  //函数式读取上下文
  const content = useContext(MyContext3)
  const total = "1001-" + content.username
  return (
    <div className="parent">
      <h3>B组件</h3>
      <h4>
        从A组件接收到的用户名:【{total}】,年龄【{content.age}】
      </h4>
      <C />
    </div>
  )
}

function C() {
  const content = useContext(MyContext3)
  return (
    <div className="child">
      <h3>C组件</h3>
      <h4>从A组件接收到的用户名:【{content.username}】</h4>
    </div>
  )
}

六、组件优化(PureComponent

extends Component问题

问题:

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

解决方案:当组件的state或props数据发生改变时才重新render()

方案一

  • 重写shouldComponentUpdate()方法,
  • 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

父组件

shouldComponentUpdate(nextProps, nextState) {
    return !(this.state.carname === nextState.carname)
 }

子组件

shouldComponentUpdate(nextProps) {
    return !(this.props.carname === nextProps.carname)
  }

方案二

使用PureComponent,重写了shouldComponentUpdate(),自动验证阀门。只有state或props数据有变化才返回true

import React, { PureComponent } from "react"
export default class Parent extends PureComponent {}

注意:

      只是进行state和props数据的浅比较, 如果只是 数据对象内部数据 变了, 返回false  

      不要直接修改state数据, 而是要产生新数据

 change = () => {
    //this.setState({ carname: "car" })
    const obj = this.state
    obj.carname = "ccc"
    console.log(this.state === obj)//true
    this.setState(obj)
  }

七、children props和render props

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

Vue中:

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

  React中:

  children props: 通过组件标签体传入结构,子组件读不到父组件的数据

import React, { Component } from "react"
import "./index.css"
export default class A extends Component {
  render() {
    return (
      <div className="A">
        <h3>A组件</h3>
        <B>
          <C />
        </B>
      </div>
    )
  }
}

class B extends Component {
  state = { name: "tom" }
  render() {
    const { name } = this.state
    return (
      <div className="B">
        <h3>B组件</h3>
        <h4>{this.props.children}</h4>
      </div>
    )
  }
}

class C extends Component {
  render() {
    return (
      <div className="C">
        <h3>C组件</h3>
      </div>
    )
  }
}

render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数(约定俗称的命名)属性。预留一个位置,把组件传过来,相当与vue的插槽

import React, { Component } from "react"
import "./index.css"
import State from "../State"
export default class A extends Component {
  render() {
    return (
      <div className="A">
        <h3>A组件</h3>
        <B render={(name) => <State name={name} />}></B>
      </div>
    )
  }
}

class B extends Component {
  state = { name: "tom" }
  render() {
    const { name } = this.state
    return (
      <div className="B">
        <h3>B组件</h3>
        {/* 预留一个位置,把state组件传过来,相当与vue的插槽 */}
        <h4>{this.props.render(name)}</h4>
      </div>
    )
  }
}

八、错误边界 

  1. 把错误限制在某一返回内,只用于生产环境。
  2. 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误。
  3. 一般都在父组件中进行设置。getDerivedStateFromError 配合componentDidCatch:
    1. getDerivedStateFromError: 当子组件出现报错时,触发调用并携带错误信息
  •  Parent组件
import React, { Component } from "react"
import Child from "./Child"
export default class Parent extends Component {
  state = {
    isError: false, //用于子组件是否产生错误
  }
  
  static getDerivedStateFromProps(error) {
    return { isError: error }
  }

  //完成渲染
  componentDidCatch(error, info) {
    console.log(error, info)
  }
  render() {
    return (
      <div>
        {this.state.isError ? <h3>当前网络不稳定,稍后再试</h3> : <Child />}
      </div>
    )
  }
}
  • Child组件
import React, { Component } from "react"

export default class Child extends Component {
  state = {
    users: [
      {
        id: "001",
        name: "tom",
        age: 18,
      },
      {
        id: "002",
        name: "nancy",
        age: 19,
      },
      {
        id: "003",
        name: "xx",
        age: 20,
      },
    ],
  }
  render() {
    const { users } = this.state
    return (
      <div>
        {users.map((obj) => {
          return (
            <h3 key={obj.id}>
              姓名:【{obj.name}】 年龄: 【{obj.age}】
            </h3>
          )
        })}
      </div>
    )
  }
}

九、组件通信方式

1. 组件间的关系

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

2. 组件间的通信方式

1. props:

      (1) children props

      (2) render props

2. 消息订阅-发布:

      pubs-sub、event等等

3. 集中式管理:

      redux、dva等等

4. Context:

      生产者-消费者模式

3.  比较好的搭配方式:

  • 父子组件:props
  • 兄弟组件:消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
React Hooks 中的 `setState` 并没有提供直接的回调函数参数,与 Class 组件中的 `setState` 不同。在处理需要在 `setState` 完成后执行回调的情况下,可以使用 `useEffect` 钩子来模拟实现。 以下是一个示例代码,展示了如何在 `setState` 完成后执行回调函数: ```jsx import React, { useState, useEffect } from 'react'; function App() { const [count, setCount] = useState(0); const handleButtonClick = () => { setCount(prevCount => prevCount + 1); }; useEffect(() => { // 这里的回调函数会在每次 count 更新后执行 console.log('Count updated:', count); }, [count]); return ( <div> <button onClick={handleButtonClick}>Increment</button> <p>Count: {count}</p> </div> ); } export default App; ``` 在上述代码中,我们通过 `useState` 定义了一个名为 `count` 的状态变量,并使用 `setCount` 进行更新。在 `handleButtonClick` 函数中,我们通过传递一个回调函数给 `setCount` 来进行更新。这个回调函数接收前一个状态值作为参数,并返回新的状态值。 在函数组件中使用 `useEffect` 钩子,并将 `count` 添加为依赖项。当 `count` 发生变化时,`useEffect` 的回调函数会被触发执行。这就实现了在 `setState` 完成后执行回调的效果。 请注意,`useEffect` 的回调函数会在组件渲染之后执行,并且每次 `count` 更新时都会触发。如果只想在组件挂载或卸载时执行一次回调,可以将依赖项数组留空。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

才不吃胡萝卜嘞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值