React--useEffect

使用 useEffect

它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Effect Hook 可以让你在函数组件中执行副作用操作

import React, { useState, useEffect } from "react";

function Counter () {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('Counter Effect -- 1 --')

    document.title = 'Counter ' + count

    return () => {
      console.log('Counter Effect -- 1 -- clear')
    }
  })

  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}> Add </button>
    </>
  )
}

该示例声明了一个函数组件,其useEffect函数会在组件每次渲染(包括首次)的时候执行,首次执行一次,控制台打印输出如下:

在这里插入图片描述

点击【Add】–》 函数的count --state变化 --》函数组件刷新 --》 useEffect清理函数 --》新的useEffect函数

什么是副作用操作?

数据网络请求、DOM操作、定时器等

有时候,在 React 更新 DOM 之后运行一些额外的代码,就可以使用一个useEffect函数去操作

比如:在组件渲染之后,需要获取后端数据,可以在该函数内部操作;创建一个定时器、倒计时弹框展示等等
有些副作用操作,需要在组件卸载之后清理(清除定时器、变量,避免内存泄露),有些就不用(数据请求)useEffect函数返回一个函数,可以在其中执行清理操作

所以,一个useEffect就相当于 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合

useEffect 依赖项

useEffect函数的第一个参数是函数,第二个参数是数组

数组中的记录的是useEffect执行依赖的变量,这些变量可以是stateprops或者是上下文等其他数据源,当依赖的变量发生变化后,useEffect才会执行,这可以作为一种性能优化

下面示例主要用于订阅聊天室人员的在线/下线状态

从最基本的开始吧,定义人员类:

class Friend {
  constructor (id, name) {
    this.id = id
    this.name = name
  }
}

class Member extends Friend {
  constructor (id, name, isOnline) {
    super(id, name)
    this.isOnline = isOnline
  }
}

定义聊天室类,添加相关方法

class ChatRoomClass {
  constructor () {
    this.members = []
    this.handlers = {}
  }

  /**
   * 加入聊天室
   * @param {Member} member 
   */
  join (member) {
    this.members.push(member)
  }

  /**
   * 改变在线状态
   * @param {number|string} id 
   */
  changeLineStatus (id) {
    const member = this.members.find(m => m.id === id)
    if (member) {
      member.isOnline = !member.isOnline
      this.trigger(id)
    }
  }

  /**
   * 退出聊天室
   * @param {Member} member 
   */
  out (member) {
    const idx = this.members.findIndex(i => i === member)
    idx > -1 && this.members.splice(idx, 1)
  }

  // 触发订阅了该 id 人员在线状态变化的函数
  trigger (id) {
    const handlerArr = this.handlers[id]
    if (!Array.isArray(handlerArr)) return void 0
    const member = this.members.find(m => m.id === id)
    handlerArr.forEach(fn => fn(member))
  }

  subscribeToFriendStatus (id, fn) {
    const member = this.members.find(m => m.id === id)
    fn(member)
    if (Array.isArray(this.handlers[id])) {
      this.handlers[id].push(fn)
    } else {
      this.handlers[id] = [fn]
    }
  }

  unsubscribeFromFriendStatus (id, fn) {
    const handlerArr = this.handlers[id]
    if (Array.isArray(handlerArr)) {
      const idx = handlerArr.findIndex(h => fn === h)
      idx > -1 && handlerArr.splice(idx, 1)
    }
  }
}

FriendStatus组件的useEffect函数中添加在线状态的订阅,在清理函数中取消订阅

function FriendStatus (props) {
  const [online, setOnline] = useState(false)

  useEffect(() => {
    console.log('FriendStatus useEffect -- excute')
    function handleStatusChange (status) {
      setOnline(status.isOnline)
    }

    ChatRoom.subscribeToFriendStatus(props.friend.id, handleStatusChange)

    return () => {
      console.log('FriendStatus useEffect -- clean up excute')
      ChatRoom.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
    }
  }, [props.friend.id])

  return (
    <>
      <span align="left">
        <strong>{props.friend.name}</strong>
        &nbsp;&nbsp;&nbsp;
        <span
          style={{
          backgroundColor: online ? '#0F0' : '#F00',
          display: 'inline-block',
          width: '12px',
          height: '12px',
          borderRadius: '50%'
        }} />
      </span>
    </>
  )
}

该组件的使用方式如下:

export default class Home extends React.Component {

  constructor (props) {
    super(props)
    this.state = {
      members: ChatRoom.members,
    }
    this.inputNameRef = React.createRef()
    this.handleJoinChat = this.handleJoinChat.bind(this)
    this.changeLineStatus = this.changeLineStatus.bind(this)
  }

  changeLineStatus (id) {
    ChatRoom.changeLineStatus(id)
    this.setState({
      members: [...ChatRoom.members]
    })
  }

  handleJoinChat () {
    const inputVal = this.inputNameRef.current.value
    if (!inputVal) return void 0
    const member = new Member(
      randomID(),
      inputVal,
      true,
    )
    ChatRoom.join(member)
    this.setState({
      members: [...ChatRoom.members]
    }, () => {
      this.inputNameRef.current.value = ''
    })
  }

  render() {
    const members = this.state.members
    const adminWindow = (
      <ul>
        {members.map(m => (
          <li key={m.id}>
            <FriendStatus friend={m} />&nbsp;&nbsp;&nbsp;
            <button onClick={() => this.changeLineStatus(m.id)}>Change</button>
          </li>
        ))}
      </ul>
    )
    const otherUserMemberSplit = members.map((item, idx, arr) => arr.filter(m => m.id !== item.id))
    const otherUserWindows = (
      <div style={{
        width: '100vw',
        display: 'flex'
      }}>
        {members.map((om, idx) => (
          <div style={{
            flex: '1 1 auto',
            borderRight: '1px solid #000'
          }} key={om.id}>
            <h4 style={{
              textDecoration: om.isOnline ? 'none' : 'line-through'
            }}>{om.name}】窗口</h4>
            <ul>
              {otherUserMemberSplit[idx].map(im => <li key={im.id}>
                <FriendStatus friend={im} />
              </li>)}
            </ul>
          </div>
        ))}
      </div>
    )
    return (
      <>
        <div>
          <input type="text" ref={this.inputNameRef} />
          <input type="button" onClick={this.handleJoinChat} value="加入" />
        </div>
        <hr />
        <h4>管理员窗口</h4>
        {adminWindow}
        <hr />
        <h4>其他用户窗口</h4>
        {otherUserWindows}
      </>
    )
  }
}

代码解释:

  1. 聊天室实例作为在线人员的数据来源,并且存在订阅取消订阅加入改变在线状态等方法
  2. Home组件中定义了整个页面的渲染情况,包含“管理员窗口”(主要用于切换某个用户的在线状态,订阅了该用户在线状态的组件更新),还有"其他人员窗口"(展示除自身以外的其他人员的在线状态订阅
  3. 改变某位用户的在线状态,其他用户窗口中,同步修改

使用效果:
在这里插入图片描述

比如:在同一个聊天室中,一个用户相当于订阅了其他所有用户的在线状态,当某一个用户的在线状态变更后,其他用户就能够及时看到该用户的状态变化

使用多个 Effect 实现关注点分离

使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题

CounterFriendStatus组合起来,互不影响:

function FriendStatusWithCounter (props) {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('Counter Effect -- 1 --')

    document.title = 'Counter ' + count

    return () => {
      console.log('Counter Effect -- 1 -- clear')
    }
  }, [count])

  const [online, setOnline] = useState(false)

  useEffect(() => {
    console.log('FriendStatusWithCounter useEffect -- excute')
    function handleStatusChange (status) {
      setOnline(status.isOnline)
    }

    ChatRoom.subscribeToFriendStatus(props.friend.id, handleStatusChange)

    return () => {
      console.log('FriendStatusWithCounter useEffect -- clean up excute')
      ChatRoom.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
    }
  }, [props.friend.id])

  return (
    <div style={{
      display: 'inline-block',
      width: 'auto',
      padding: '10px',
      border: '1px solid #AAA',
      marginBottom: '10px',
    }}>
      <div align="left">
        <strong>{props.friend.name}</strong>
        &nbsp;&nbsp;&nbsp;
        <span
          style={{
          backgroundColor: online ? '#0F0' : '#F00',
          display: 'inline-block',
          width: '12px',
          height: '12px',
          borderRadius: '50%'
        }} />
      </div>
      {!props.admin && <div>
        <strong>Count: {count}</strong>&nbsp;&nbsp;&nbsp;
        <button onClick={() => setCount(count + 1)}> Add </button>
      </div>}
    </div>
  )
}

完整代码如下:

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

function randomID () {
  return Math.random().toString(36).slice(-6)
}


class Friend {
  constructor (id, name) {
    this.id = id
    this.name = name
  }
}

class Member extends Friend {
  constructor (id, name, isOnline) {
    super(id, name)
    this.isOnline = isOnline
  }
}

class ChatRoomClass {
  constructor () {
    this.members = []
    this.handlers = {}
  }

  /**
   * 加入聊天室
   * @param {Member} member 
   */
  join (member) {
    this.members.push(member)
  }

  /**
   * 改变在线状态
   * @param {number|string} id
   */
  changeLineStatus (id) {
    const member = this.members.find(m => m.id === id)
    if (member) {
      member.isOnline = !member.isOnline
      this.trigger(id)
    }
  }

  /**
   * 退出聊天室
   * @param {Member} member 
   */
  out (member) {
    const idx = this.members.findIndex(i => i === member)
    idx > -1 && this.members.splice(idx, 1)
  }

  // 触发订阅了该 id 人员在线状态变化的函数
  trigger (id) {
    const handlerArr = this.handlers[id]
    if (!Array.isArray(handlerArr)) return void 0
    const member = this.members.find(m => m.id === id)
    handlerArr.forEach(fn => fn(member))
  }

  subscribeToFriendStatus (id, fn) {
    const member = this.members.find(m => m.id === id)
    fn(member)
    if (Array.isArray(this.handlers[id])) {
      this.handlers[id].push(fn)
    } else {
      this.handlers[id] = [fn]
    }
  }

  unsubscribeFromFriendStatus (id, fn) {
    const handlerArr = this.handlers[id]
    if (Array.isArray(handlerArr)) {
      const idx = handlerArr.findIndex(h => fn === h)
      idx > -1 && handlerArr.splice(idx, 1)
    }
  }
}

const ChatRoom = new ChatRoomClass()


function FriendStatusWithCounter (props) {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('Counter Effect -- 1 --')

    document.title = 'Counter ' + count

    return () => {
      console.log('Counter Effect -- 1 -- clear')
    }
  }, [count])

  const [online, setOnline] = useState(false)

  useEffect(() => {
    console.log('FriendStatusWithCounter useEffect -- excute')
    function handleStatusChange (status) {
      setOnline(status.isOnline)
    }

    ChatRoom.subscribeToFriendStatus(props.friend.id, handleStatusChange)

    return () => {
      console.log('FriendStatusWithCounter useEffect -- clean up excute')
      ChatRoom.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange)
    }
  }, [props.friend.id])

  return (
    <div style={{
      display: 'inline-block',
      width: 'auto',
      padding: '10px',
      border: '1px solid #AAA',
      marginBottom: '10px',
    }}>
      <div align="left">
        <strong>{props.friend.name}</strong>
        &nbsp;&nbsp;&nbsp;
        <span
          style={{
          backgroundColor: online ? '#0F0' : '#F00',
          display: 'inline-block',
          width: '12px',
          height: '12px',
          borderRadius: '50%'
        }} />
      </div>
      {!props.admin && <div>
        <strong>Count: {count}</strong>&nbsp;&nbsp;&nbsp;
        <button onClick={() => setCount(count + 1)}> Add </button>
      </div>}
    </div>
  )
}

export default class Home extends React.Component {

  constructor (props) {
    super(props)
    this.state = {
      members: ChatRoom.members,
    }
    this.inputNameRef = React.createRef()
    this.handleJoinChat = this.handleJoinChat.bind(this)
    this.changeLineStatus = this.changeLineStatus.bind(this)
  }

  changeLineStatus (id) {
    ChatRoom.changeLineStatus(id)
    this.setState({
      members: [...ChatRoom.members]
    })
  }

  handleJoinChat () {
    const inputVal = this.inputNameRef.current.value
    if (!inputVal) return void 0
    const member = new Member(
      randomID(),
      inputVal,
      true,
    )
    ChatRoom.join(member)
    this.setState({
      members: [...ChatRoom.members]
    }, () => {
      this.inputNameRef.current.value = ''
    })
  }

  render() {
    const members = this.state.members
    const adminWindow = (
      <ul>
        {members.map(m => (
          <li key={m.id}>
            <FriendStatusWithCounter admin friend={m} />&nbsp;&nbsp;&nbsp;
            <button onClick={() => this.changeLineStatus(m.id)}>Change</button>
          </li>
        ))}
      </ul>
    )
    const otherUserMemberSplit = members.map((item, idx, arr) => arr.filter(m => m.id !== item.id))
    const otherUserWindows = (
      <div style={{
        width: '100vw',
        display: 'flex'
      }}>
        {members.map((om, idx) => (
          <div style={{
            flex: '1 1 auto',
            borderRight: '1px solid #000'
          }} key={om.id}>
            <h4 style={{
              textDecoration: om.isOnline ? 'none' : 'line-through'
            }}>{om.name}】窗口</h4>
            <ul>
              {otherUserMemberSplit[idx].map(im => <li key={im.id}>
                <FriendStatusWithCounter friend={im} />
              </li>)}
            </ul>
          </div>
        ))}
      </div>
    )
    return (
      <>
        <div>
          <input type="text" ref={this.inputNameRef} />
          <input type="button" onClick={this.handleJoinChat} value="加入" />
        </div>
        <hr />
        <h4>管理员窗口</h4>
        {adminWindow}
        <hr />
        <h4>其他用户窗口</h4>
        {otherUserWindows}
      </>
    )
  }
}

小结

  • useStateuseEffectHooks用于在函数组件中勾入相当于类组件生命周期的功能,使函数组件具有了状态
  • useEffect函数用于处理组件中的副作用,副作用包括数据获取、DOM操作、定时器等,当然不仅限于此。它相当于 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合
  • 还可以使用useEffect实现分离关注点,抽离出细致的功能,封装成函数在适当地时机执行
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值