WHAT - React 学习系列(七)- Escape hatches

Overview

Some of your components may need to control and synchronize with systems outside of React. For example, you might need to focus an input using the browser API, play and pause a video player implemented without React, or connect and listen to messages from a remote server.

Referencing values with refs

When you want a component to "remember" some information, but you don't want that information to trigger new renders, you can use a ref. A ref is like a secret pocket of your component that React doesn't track. For example, you can use refs to store timeout IDs, DOM elements, and other objects that don't impact the component's rendering output.

Manipulating the DOM with refs

Sometimes you might need access to the DOM elements managed by React---for example, to focus a node, scroll to it, or measure its size and position. There is no built-in way to do those things in React, so you will need a ref to the DOM node.

For example, clicking the button will focus the input using a ref:

javascript 复制代码
import { useRef } from 'react';
export default function Form() {
  const inputRef = useRef(null);
  function handleClick() {
    inputRef.current.focus();
  }
  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

Synchronizing with external systems with Effects

Some components need to synchronize with external systems.

For example, you might want to control a non-React component based on the React state, set up a server connection, or send an analytics log when a component appears on the screen.

Unlike event handlers, which let you handle particular events, Effects let you run some code after rendering. Use them to synchronize your component with a system outside of React.

javascript 复制代码
import { useState, useRef, useEffect } from 'react';
function VideoPlayer({ src, isPlaying }) {
  const ref = useRef(null);
  useEffect(() => {
    if (isPlaying) {
      ref.current.play();
    } else {
      ref.current.pause();
    }
  }, [isPlaying]);

  return <video ref={ref} src={src} loop playsInline />;
}
export default function App() {
  const [isPlaying, setIsPlaying] = useState(false);
  return (
    <>
      <button onClick={() => setIsPlaying(!isPlaying)}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>
      <VideoPlayer
        isPlaying={isPlaying}
        src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
      />
    </>
  );
}

Many Effects also "clean up" after themselves.For example, an Effect that sets up a connection to a chat server should return a cleanup function that tells React how to disconnect your component from that server.

javascript 复制代码
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';
export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, []);
  return <h1>Welcome to the chat!</h1>;
}

In development , React will immediately run and clean up your Effect one extra time. This is why you see "Connecting..." printed twice. This ensures that you don't forget to implement the cleanup function.

Effects are an escape hatch from the React paradigm. They let you "step outside" of React and synchronize your components with some external system.

You Might Not Need An Effect

If there is no external system involved (for example, if you want to update a component's state when some props or state change), you shouldn't need an Effect.

Removing unnecessary Effects will make your code easier to follow, faster to run, and less error-prone.

There are two common cases in which you don't need Effects:

  • You don't need Effects to transform data for rendering.
  • You don't need Effects to handle user events.
javascript 复制代码
  // 🔴 Avoid: redundant state and unnecessary Effect
  const [fullName, setFullName] = useState('');
  useEffect(() => {
    setFullName(firstName + ' ' + lastName);
  }, [firstName, lastName]);

Lifecycle of reactive effects

Effects have a different lifecycle from components.

Components may mount, update, or unmount.

An Effect can only do two things: to start synchronizing something, and later to stop synchronizing it.

This cycle can happen multiple times if your Effect depends on props and state that change over time.

javascript 复制代码
function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

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

This Effect depends on the value of the roomId prop. Props are reactive values, which means they can change on a re-render.

React provides a linter rule to check that you've specified your Effect's dependencies correctly. If you forget to specify roomId in the list of dependencies in the above example, the linter will find that bug automatically.

When you write an Effect, the linter will verify that you've included every reactive value (like props and state) that the Effect reads in the list of your Effect's dependencies. This ensures that your Effect remains synchronized with the latest props and state of your component.

Separating events from Effects: useEffectEvent

Event handlers only re-run when you perform the same interaction again. Unlike event handlers, Effects re-synchronize if any of the values they read, like props or state, are different than during last render. Sometimes, you want a mix of both behaviors: an Effect that re-runs in response to some values but not others?

All code inside Effects is reactive. It will run again if some reactive value it reads has changed due to a re-render. For example, this Effect will re-connect to the chat if either roomId or theme have changed:

javascript 复制代码
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>
}

This is not ideal. You want to re-connect to the chat only if the roomId has changed. Move the code reading theme out of your Effect into an Effect Event:

javascript 复制代码
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>
}

Code inside Effect Events isn't reactive, so changing the theme no longer makes your Effect re-connect.

Removing Effect dependencies

Unnecessary dependencies may cause your Effect to run too often, or even create an infinite loop.

For example, this Effect depends on the options object which gets re-created every time you edit the input:

javascript 复制代码
  const options = {
    serverUrl: serverUrl,
    roomId: roomId
  };
  useEffect(() => {
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [options]);

Error - 9:9 - The 'options' object makes the dependencies of useEffect Hook (at line 18) change on every render. Move it inside the useEffect callback. Alternatively, wrap the initialization of 'options' in its own useMemo() Hook.

To fix this problem, move creation of the options object inside the Effect so that the Effect only depends on the roomId string:

javascript 复制代码
  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

Reusing logic with custom Hooks

Sometimes, you'll wish that there was a Hook for some more specific purpose: for example, to fetch data, to keep track of whether the user is online, or to connect to a chat room.

To do this, you can create your own Hooks for your application's needs.

You can create custom Hooks, compose them together, pass data between them, and reuse them between components. As your app grows, you will write fewer Effects by hand because you'll be able to reuse custom Hooks you already wrote.

There are also many excellent custom Hooks maintained by the React community.

Referencing values with refs

Manipulating the DOM with refs

Synchronizing with external systems with Effects

You Might Not Need An Effect

Lifecycle of reactive effects

Separating events from Effects: useEffectEvent

Removing Effect dependencies

Reusing logic with custom Hooks

相关推荐
我命由我123451 分钟前
SSL 协议(HTTPS 协议的关键)
网络·经验分享·笔记·学习·https·ssl·学习方法
HEX9CF14 分钟前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
丶Darling.41 分钟前
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树
开发语言·数据结构·c++·笔记·学习·算法
积水成江44 分钟前
关于Generator,async 和 await的介绍
前端·javascript·vue.js
Z3r4y1 小时前
【Web】portswigger 服务端原型污染 labs 全解
javascript·web安全·nodejs·原型链污染·wp·portswigger
人生の三重奏1 小时前
前端——js补充
开发语言·前端·javascript
Tandy12356_1 小时前
js逆向——webpack实战案例(一)
前端·javascript·安全·webpack
TonyH20021 小时前
webpack 4 的 30 个步骤构建 react 开发环境
前端·css·react.js·webpack·postcss·打包
百里香酚兰1 小时前
【AI学习笔记】基于Unity+DeepSeek开发的一些BUG记录&解决方案
人工智能·学习·unity·大模型·deepseek
老华带你飞1 小时前
公寓管理系统|SprinBoot+vue夕阳红公寓管理系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·spring boot·课程设计