Published on

Effect 相关

Authors
  • avatar
    Name
    李丹秋
    Twitter

Effect 允许你指定由渲染本身,而不是特定事件引起的副作用, Effect的触发时机是在视图更新之后。

你可能不需要Effect

什么时候需要Effect

effect通常用于与一些外部的系统进行同步,比如浏览器API、三方DOM、网络请求等。如果只想根据状态而调整某些状态,那么是不需要Effect的

Effect特性

默认情况下React每次重新渲染之后,都会触发Effect.所以应该避免在Effect中执行State相关操作。

  1. 如果没有传递依赖项数组,则每次重新渲染都会触发Effect
  2. 如果依赖项是空数组,则Effect只执行一次
  3. 如果依赖项数组中有明确的State变量,则只有相关State发生改变的时候,才会执行Effect 你无法选择依赖项,依赖项完全取决于Effect中的代码

为什么依赖数组中,可以省略Ref?

function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);
  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

因为Ref对象是一个稳定的标识符,React保证每次渲染Ref对象的引用都是相同的,永远不会改变,所有依赖数组中写不写都无所谓。 如果一些函数只需要执行一次,完全可以放在渲染函数中执行,而不是在Effect中

如何计算计算耗时?

console.time('filter array');
const visibleTodos = getFilteredTodos(todos, filter);
console.timeEnd('filter array');

console.time('filter array');
const visibleTodos = useMemo(() => {
  return getFilteredTodos(todos, filter); // Skipped if todos and filter haven't changed
}, [todos, filter]);
console.timeEnd('filter array');

什么时候不需要Effect?

遵循一个大的原则,凡是事件触发的形式,都可以不需要Effect,Effect只去处理渲染所依赖的情况了

响应式Effect的生命周期

React如何重新同步Effect

  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        选择聊天室:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">所有</option>
          <option value="travel">旅游</option>
          <option value="music">音乐</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? '关闭聊天' : '打开聊天'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}
  1. 当依赖项目发生改变的时候,会先去执行卸载的操作,然后重新执行链接
  2. 当组件卸载的时候,断开链接 在严格模式下,Effect会执行两次,目的就是暴露潜在的风险,比如可能没有卸载相关链接

React会验证Effect代码中使用的每个响应式值是否已声明为其依赖项。如果一些值不会因为重新渲染而改变,则可以将他们移动到组件外部或者Effect内部。 写代码的时候要着重关注Effect依赖检查

将事件从Effect中分离

import { useState, useEffect } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      showNotification('Connected!', theme);
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId, theme]);

  return <h1>Welcome to the {roomId} room!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Use dark theme
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

这种情况当theme改变的时候,聊天也会重练,这肯定是不符合我们预期的。所以需要将showNotification这个非响应式的逻辑和周围Effect隔离开来。

import { useState, useEffect } from 'react';
import { experimental_useEffectEvent as useEffectEvent } from 'react';
import { createConnection, sendMessage } from './chat.js';
import { showNotification } from './notifications.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId, theme }) {
  const onConnected = useEffectEvent(() => {
    showNotification('Connected!', theme);
  });

  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.on('connected', () => {
      onConnected();
    });
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Welcome to the {roomId} room!</h1>
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [isDark, setIsDark] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <label>
        <input
          type="checkbox"
          checked={isDark}
          onChange={e => setIsDark(e.target.checked)}
        />
        Use dark theme
      </label>
      <hr />
      <ChatRoom
        roomId={roomId}
        theme={isDark ? 'dark' : 'light'}
      />
    </>
  );
}

可以将useEffectEvent类比成为一个事件,它发生在某个准确的时刻,而不是依赖于Effect响应