跳到主要内容

组件✅

构建组件的方式有哪些?

答案
类型用法适说明
函数组件function MyComp(props) { ... }推荐主流写法,配合 Hooks
类组件class MyComp extends React.Component需要完整生命周期、复杂逻辑
类组件继承 PureComponentclass MyComp extends React.PureComponent优化了性能,避免会做浅比对,相同就不会渲染
高阶组件(HOC)withXXX(WrappedComp)逻辑复用、权限、日志等增强
函数作为子组件<Comp>{fn => ...}</Comp>Render Props,灵活渲染
React.cloneElementReact.cloneElement(child, props)动态增强/注入子元素
React.createElementReact.createElement(type, props, ...)JSX 底层实现,动态创建元素

补充说明

  • 实际开发优先推荐函数组件+Hooks,HOC/cloneElement等用于特殊增强场景。
  • createElement 通常由 JSX 自动调用,手写较少。
提示

如需灵活复用渲染逻辑,优先考虑 Render Props 或 HOC。

延伸阅读

类组件继承 Component 和 PureComponent 有什么区别?

答案

PureComponent 源码 相比 Component 在原型上多了一个 pureComponentPrototype.isPureReactComponent = true; 用来标识为纯类组件。

然后在更新检查时,如果判断为纯组件,会对属性和状态进行浅比较,如果相同则不触发更新。具体代码

checkShouldComponentUpdate

function checkShouldComponentUpdate () {
// ...
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
)
}
}
提示

注意浅比较内部采用的是 shallowEqual 方法,核心逻辑如下

//  true 相同,即使是引用不同,因为浅层值都是原始值,且值相同,不会触发更新,
shallowEqual({ a: 1 }, { a: 1 })

// false 不同,因为浅层值中 a 对应的不是同一个引用对象即使值没有变化,会触发更新,
shallowEqual({ a: { b: 1 } }, { a: { b: 1 } })

const a = { a: 1, c: { b: 2 } }
const b = { a: 1, c: a.c }
// true 相同,因为浅层值中所有原始值和引用值都相同,不会触发更新,
shallowEqual(a, b)

这也是为什么在使用 PureComponent 时需要注意不要直接修改引用对象的原因。还有值看起来相等,但也会触发刷新的问题

类组件 和 Function Components 的区别?

答案
维度类组件函数组件对比说明
编程思想面向对象(OOP)函数式 + 声明式函数组件更贴合 React 的核心理念:UI = f(state)
状态管理this.state + setState()useState()函数组件语法更简洁,更新逻辑更清晰
副作用处理多个生命周期函数useEffect()、useLayoutEffect()函数组件副作用逻辑聚合,更集中
逻辑复用HOC / render props自定义 HookHook 更灵活、组合性强,避免嵌套地狱
代码结构构造器 + 多方法 + this单函数 + Hook,无 this函数组件更少样板代码,作用域更明确
性能优化shouldComponentUpdate / PureComponentReact.memo / useMemo / useCallback函数组件提供更细粒度的性能控制
官方态度兼容保留,逐渐淡出默认推荐,重点支持文档、生态和新特性均围绕函数组件构建

此外函数组件在访问是的是状态的快照,而类组件由于通过 this 访问 prop 和 state, 所以访问的是最新的值。示例如下,更具体的描述详见 How Are Function Components Different from Classes? React 核心开发 dan abramov 讲解函数组件和类组件的核心差异

import FunctionComponent from './FunctionComponent'
import ClassComponent from './ClassComponent'
import { useState } from 'react'

export default function App () {
  const [value, setValue] = useState('A')
  return (
    <div>
      <button onClick={() => setValue(v => v === 'A' ? 'B' : 'A')}>切换 value {value}</button>
      <FunctionComponent value={value} />
      <ClassComponent value={value} />
    </div>
  )
}

提示

在组件渲染出来后点击切换状态,查看控制台输出可以看到函数组件输出的是状态切换前的快照,所以为 A, 类组件输出的是最新的状态,所以为 B

受控和非受控组件区别?

答案

参考官方文档 受控和非受控组件

  • 受控组件 React 控制元素状态为受控组件
  • 非受控组件 不受 react 控制的元素
实时编辑器
// 受控组件
function ControlledForm () {
  const [value, setValue] = useState('')
  // react 控制组件的状态
  return (
    <input
      value={value}
      onChange={e => setValue(e.target.value)}
    />
  )
}
结果
Loading...

React 有哪些内置组件?

答案

React 有两个核心包

  • react 暴露了脱离平台的核心 API 和功能
  • react-dom 提供了与 DOM 相关的 API 和功能

React 相关内置组件

组件说明
<Fragment>、<>实现无根节点的列表渲染
<Profiler>性能分析组件,记录渲染时间和更新信息
<StrictMode>严格模式组件,帮助发现潜在问题
<Suspense>懒加载组件,处理异步加载
<Activity>实验性质,用于跟踪组件活动状态
<ViewTransition>实验性质,用于处理视图过渡动画

ReactDOM 并未暴露任何内置组件,而是对 DOM 原生组件进行了增强,核心功能如下

组件说明
div等组件例如 ref 实现获取组件实例,className 实现类名绑定等,事件会被代理为合成事件
<form>action 属性除了 url, 还支持函数形式,提交时会调用函数并传入表单数据
<input>formAction 除了 url 同样支持函数,表单上事件会被代理为合成事件

说一下 Suspense 和 lazy 的使用场景?

答案

suspense 用来处理异步加载的组件,通过 fallback 属性指定加载时的占位内容。lazy 用来懒加载组件,只有在需要时才加载通常可以配合 Suspense 使用。

import { Suspense, lazy } from 'react'

export default function App () {
  const LazyComponent = lazy(() => import('./LazyComponent'))

  return (
    <Suspense fallback={<div>Loading data...</div>}>
      <LazyComponent />
    </Suspense>
  )
}

延伸阅读

createPortal 了解多少?

答案

createPortal 解决需要把组件绑定在特定 DOM 节点上的问题,通常用于模态框、提示框等场景。

import { useState } from 'react'
import { createPortal } from 'react-dom'

const Modal = ({ isOpen, onClose, children }) => {
  if (!isOpen) return null

  return createPortal(
      <div className="modal-overlay" onClick={onClose}>
         <div className="modal-content" onClick={e => e.stopPropagation()}>
            {children}
            <button onClick={onClose}>Close</button>
         </div>
      </div>,
      document.body
  )
}

const App = () => {
  const [isModalOpen, setIsModalOpen] = useState(false)

  return (
      <div className="app">
         <h1>Modal Demo</h1>
         <button onClick={() => setIsModalOpen(true)}>Open Modal</button>

         <Modal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
            <h2>Modal Content</h2>
            <p>This is rendered using createPortal</p>
         </Modal>

         <style>{`
            .modal-overlay {
               position: fixed;
               top: 0;
               left: 0;
               right: 0;
               bottom: 0;
               background-color: rgba(0, 0, 0, 0.5);
               display: flex;
               justify-content: center;
               align-items: center;
            }

            .modal-content {
               background: white;
               padding: 20px;
               border-radius: 4px;
               max-width: 500px;
               width: 90%;
            }
         `}</style>
      </div>
  )
}

export default App

延伸阅读

说一下 ViewTransition 的使用 ?

答案

延伸阅读

组件生命周期

答案

参考 如何理解 React 里面的生命周期

  1. 挂载阶段(Mounting)
    • constructor:组件实例化时执行,用于初始化 state 绑定事件等操作
    • getDerivedStateFromProps:在render方法执行之前调用,用于根据props设置state。
    • render 渲染组件
    • componentDidMount:组件挂载到DOM后执行,用于执行一些需要DOM的操作,如获取数据。
  2. 更新阶段(Updating)
  3. 卸载阶段(Unmounting)
  4. 异常流程会触发如下钩子

此外还废弃了如下钩子

参考 class lifecycle

对于函数组件, 重点钩子映射如下

  • useLayoutEffect ,在 DOM 更新后立即执行,模拟 componentDidMount 和 componentDidUpdate 行为
  • useEffect 副作用钩子,每次挂载和更新后都会触发,通过返回的函数来清理副作用模拟

参考 react-hooks-lifecycle

进一步细节参考 react-hook-component-timeline

示例代码

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React 生命周期演示</title>
    <style>
      .lifecycle-demo {
        max-width: 600px;
        margin: 20px auto;
        padding: 20px;
        font-family: Arial, sans-serif;
      }
      .controls {
        margin: 20px 0;
      }
      .controls button {
        margin-right: 10px;
        padding: 8px 16px;
      }
      .class-component {
        padding: 20px;
        border: 1px solid #eee;
        border-radius: 4px;
        margin: 20px 0;
      }
      .lifecycle-phase {
        margin: 10px 0;
        padding: 10px;
        border: 1px solid #eee;
        border-radius: 4px;
      }
      .lifecycle-phase h4 {
        margin: 0 0 10px 0;
        color: #333;
      }
      .lifecycle-phase ol {
        margin: 0;
        padding-left: 20px;
      }
      .lifecycle-phase li {
        margin: 5px 0;
        color: #666;
      }
      .component-status {
        margin-top: 20px;
        padding: 10px;
        background-color: #f5f5f5;
        border-radius: 4px;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

    <script type="text/babel" data-type="module">
      import React, { Component } from "https://esm.sh/react@19";
      import ReactDOM from "https://esm.sh/react-dom@19/client";

      // 内部类组件 - 用于演示生命周期
      class ClassComponent extends Component {
        // 添加默认属性
        static defaultProps = {
          defaultTime: new Date().toLocaleTimeString()
        };

        // 添加上下文类型
        static contextType = React.createContext('light');

        constructor(props) {
          super(props);
          console.log("Constructor: 初始化状态");
        }

        static getDerivedStateFromProps(nextProps, prevState) {
          console.log("getDerivedStateFromProps: 从props获取state", {nextProps, prevState});
          return null;
        }

        shouldComponentUpdate(nextProps, nextState, nextContext) {
          console.log("shouldComponentUpdate: 判断是否需要重新渲染", {
            nextProps,
            nextState,
            nextContext
          });
          return true;
        }

        getSnapshotBeforeUpdate(prevProps, prevState) {
          console.log("getSnapshotBeforeUpdate: 获取更新前的快照", {prevProps, prevState});
          return { scrollPos: 0 }; // 示例返回值
        }

        componentDidMount() {
          console.log("componentDidMount: 组件已挂载");
        }

        componentDidUpdate(prevProps, prevState, snapshot) {
          console.log("componentDidUpdate: 组件已更新", {
            prevProps,
            prevState,
            snapshot
          });
        }

        componentWillUnmount() {
          console.log("componentWillUnmount: 组件即将卸载");
        }

        static getDerivedStateFromError(error) {
          console.log("getDerivedStateFromError: 从错误中派生状态", error);
          return { error: error.message };
        }

        componentDidCatch(error, info) {
          console.log("componentDidCatch: 捕获到错误", {
            error,
            info
          });
        }

        render() {
          console.log("render: 渲染组件", {
            props: this.props,
          });

          return (
            <div className="class-component">
              <h3>类组件生命周期</h3>
              <div className="component-status">
                <p>当前状态: {this.props.mounted ? "已挂载" : "未挂载"}</p>
                <p>最后更新: {this.props.updateTime}</p>
              </div>
            </div>
          );
        }
      }

      // 外层容器组件 - 控制类组件的生命周期
      class LifecycleDemo extends Component {
        constructor(props) {
          super(props);
          this.state = {
            mounted: false,
            updateTime: new Date().toLocaleTimeString(),
          };
        }

        handleMount = () => {
          if (!this.state.mounted) {
            console.log('%c=== 挂载阶段 ===', 'color: green; font-weight: bold;'); 
            this.setState({
              mounted: true,
              updateTime: new Date().toLocaleTimeString(),
            });
          }
        };

        handleUpdate = () => {
          if (this.state.mounted) {
            console.log('%c=== 更新阶段 ===', 'color: blue; font-weight: bold;');
            this.setState({
              updateTime: new Date().toLocaleTimeString(),
            });
          }
        };

        handleUnmount = () => {
          if (this.state.mounted) {
            console.clear();
            console.log('%c=== 卸载阶段 ===', 'color: red; font-weight: bold;');
            this.setState({ mounted: false });
          }
        };

        render() {
          return (
            <div className="lifecycle-demo">
              <h2>React 生命周期演示</h2>
              <div className="controls">
                <button onClick={this.handleMount}>Mount</button>
                <button onClick={this.handleUpdate}>Update</button>
                <button onClick={this.handleUnmount}>Unmount</button>
              </div>

              {this.state.mounted && (
                <ClassComponent
                  {...this.state}
                />
              )}

              <div className="note">
                <p>请打开控制台查看生命周期钩子的触发顺序</p>
              </div>
            </div>
          );
        }
      }

      // 渲染组件
      const container = document.getElementById("root");
      const root = ReactDOM.createRoot(container);
      root.render(<LifecycleDemo />);
    </script>
  </body>
</html>

延伸阅读

父组件如何调用子组件的方法?

答案

父组件调用子组件方法,关键在于获取子组件实例或暴露接口。常见方式如下:

场景/方式适用组件类型典型写法/说明优缺点说明
ref(createRef)类组件<Sub ref={this.sub} />this.sub.current.fn()简单直观,HOC包裹时失效
ref函数式声明类组件<Sub ref={ref => this.sub = ref} />写法简洁,HOC包裹时失效
props onRef类组件/HOC<Sub onRef={node => this.Sub = node} />兼容HOC,需自定义props
useImperativeHandle + forwardRef函数组件/HOCuseImperativeHandle(ref, () => ({ fn }))推荐函数组件,灵活可控

代码示例

// 类组件 ref
class Sub extends React.Component {
callback() { console.log('类组件方法') }
render() { return <div>子组件</div> }
}
class Super extends React.Component {
sub = React.createRef()
handleClick = () => { this.sub.current.callback() }
render() { return <Sub ref={this.sub} /> }
}

// 函数组件 useImperativeHandle
const Child = React.forwardRef((props, ref) => {
React.useImperativeHandle(ref, () => ({
func: () => console.log('函数组件方法')
}))
return <div>子组件</div>
})
function Parent() {
const childRef = React.useRef()
React.useEffect(() => { childRef.current.func() }, [])
return <Child ref={childRef} />
}

常见误区

  • HOC 包裹的子组件 ref 失效,需用 onRef 或 forwardRef。
  • 函数组件不能直接用 ref 访问方法,需配合 useImperativeHandle。
提示

如需兼容 HOC 或函数组件,推荐用 forwardRef + useImperativeHandle 或自定义 onRef props。

延伸阅读

react 组件通信方式

答案

默认情况下 react 推荐采用单向数据流,父组件通过 props 向子组件传递数据,子组件通过回调函数向父组件传递数据。除了标准的 props 传递外,还有如下几种方式可以实现组件之间的通信。

  1. props drilling:通过 props 从父组件传递数据到子组件,逐层传递数据。这种方式简单直接,但是当组件层级较深时,会导致 props 传递过程繁琐和组件耦合度增加。
  2. context:使用 React 的 context API,可以实现跨层级的数据传递,避免 props drilling 的问题。通过创建 context 对象,可以在组件树中传递数据,任何一个组件都可以访问到这个数据。但是 context 会使组件的复用性降低,因为组件和 context 之间会产生耦合。
  3. 事件总线:通过事件总线的方式,可以实现任意组件之间的通信。事件总线是一个全局的事件系统,组件可以通过订阅和发布事件来进行通信。但是事件总线会使组件之间的关系变得不明确,不易维护。
  4. Redux/Mobx:使用状态管理库,如 Redux 或 Mobx,可以实现全局状态管理,任意组件都可以访问和修改全局状态。这种方式适用于大型应用,但是引入了额外的复杂性和学习成本。

本质上组件之间的通信就是信息交换的过程,在脱离框架范式的逻辑上,状态的管理可以不仅局限于内存模式,本地存储、URL、远程都可以作为状态的存储介质。

延伸阅读

请求在哪个阶段发出,如何取消请求?

答案
  1. 类组件
    1. componentDidMount 中发起请求,组件挂载后执行。
    2. componentWillUnmount 中取消请求,组件卸载前执行
  2. 函数组件
    1. useEffect 中发起请求,组件挂载后执行。
    2. useEffect 的清理函数中取消请求,组件卸载前执行。

示例代码

import { useState } from 'react'
import UserDataClass from './ClassDemo'
import UserDataFunction from './FunctionDemo'

// 主组件:展示两种方式
function App () {
  const [show, setShow] = useState(true)

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial' }}>
      <h1>React 请求发起与取消示例</h1>
      <button onClick={() => setShow(!show)}>
        {show ? '卸载组件(取消请求)' : '挂载组件(重新发起请求)'}
      </button>

      {show && (
        <>
          <UserDataClass />
          <UserDataFunction />
        </>
      )}
    </div>
  )
}

export default App

jsx 返回 null undefined false true 区别?

答案

在 react 中若返回 null, undefined, false, true 均表示空渲染。逻辑上无区别

function EmptyRenderNull () {
  return null
}

function EmptyRenderUndefined () {
  return undefined
}
function EmptyRenderFalse () {
  return false
}
function EmptyRenderTrue () {
  return true
}

function App () {
  return (<>
      <p>JSX 返回 null、undefined、false, true 均为空</p>
      <EmptyRenderNull />
      <EmptyRenderUndefined />
      <EmptyRenderFalse />
      <EmptyRenderTrue />
      <p>JSX 渲染 null、undefined、false, true 均为空</p>
      {null}
      {undefined}
      {false}
      {true}
      {0}
      {''}
      <p>注意在条件渲染的时候确保条件为 boolean 值,避免 0 和 '' 被当成 false</p>
      {0 && '0 被错误渲染'}
      {'' && '空字符串被错误渲染'}
   </>)
}

export default App

提示

有一些误导性的文章会说 false 导致再也不会触发更新而 null 不会,实际上 false 也会触发更新,注意甄别

import React from 'react'

const Button = (props) => {
  console.log('render', props.onClick)

  if (props.mode === 'null') return null
  if (props.mode === 'false') return false
  if (props.mode === 'undefined') return undefined // ⚠️ React 会报错
  if (props.mode === 'true') return true // ⚠️ React 会报错

  return <button onClick={props.onClick}>Click me</button>
}

const App = () => {
  const [mode, setMode] = React.useState('null')

  return (
    <div>
      <button onClick={() => setMode('null')}>null</button>
      <button onClick={() => setMode('false')}>false</button>
      <button onClick={() => setMode('undefined')}>undefined</button>
      <button onClick={() => setMode('true')}>true</button>
      <button onClick={() => setMode('button')}>show button</button>

      <Button mode={mode} onClick={() => alert('clicked')} />
    </div>
  )
}

export default App

延伸阅读

JSX 如何渲染 HTML 注释节点 ?

答案

可以采用 dangerouslySetInnerHTML ,新版文档参见 Dangerously setting the inner HTML


const CommentNode = ({ text }) => {
return (
<div
style={{ display: "none" }}
dangerouslySetInnerHTML={{ __html: `<!-- ${text} -->` }}
/>
);
};


React.Children.map 和 props.children 的区别

答案

Children 是用来处理组件子元素的工具类,提供了一些方法来操作和遍历子元素。Children.map 是其中一个方法,用于遍历子元素并对每个子元素执行一个函数。

  1. Children.map 会自动兼容各种场景,包括单个子元素、多个子元素、数组空等情况,children.map 需自行判断传入的类型做兼容
import React from 'react'

function ChildrenMapDemo ({ children }: { children: React.ReactNode }) {
  return React.Children.map(children, (child, index) => {
    return React.cloneElement(child as React.ReactElement, {
      key: index,
      style: { border: '1px solid #ccc', padding: '5px', margin: '5px' }
    })
  })
}

function ChildrenMapDemo1 ({ children }: { children: React.ReactNode }) {
  if (children === null || children === undefined) {
    return null
  }
  if (!Array.isArray(children)) {
    return React.cloneElement(children as React.ReactElement, {
      style: { border: '1px solid #ccc', padding: '5px', margin: '5px' }
    })
  } else {
    return children.map((child, index) => {
      return React.cloneElement(child as React.ReactElement, {
        key: index,
        style: { border: '1px solid #ccc', padding: '5px', margin: '5px' }
      })
    })
  }
}

function App () {
  return (
    <div>
      <p>采用 Children.map 会自动兼容个场景</p>
      <ChildrenMapDemo />
      <ChildrenMapDemo>
        <div>Child 1</div>
      </ChildrenMapDemo>
      <ChildrenMapDemo>
        <div>Child 1</div>
        <div>Child 2</div>
      </ChildrenMapDemo>
      <p>采用 children.map 需要自己判断 children 的类型避免错误</p>
      <ChildrenMapDemo1 />
      <ChildrenMapDemo1>
        <div>Child 1</div>
      </ChildrenMapDemo1>
      <ChildrenMapDemo1>
        <div>Child 1</div>
        <div>Child 2</div>
      </ChildrenMapDemo1>
    </div>
  )
}

export default App

  1. Children.map 会自动处理 key 属性,而 children.map 需要手动处理 key 属性
import { Children } from 'react'

function RowList ({ children }) {
  return (
    <div className="RowList">
      {Children.map(children, child =>
        <div style={{ border: '1px solid #ccc', padding: '5px', margin: '5px' }} className="Row">
          {child}
        </div>
      )}
    </div>
  )
}

function RowListWithChildren ({ children }) {
  return (
    <div className="RowList">
      {children.map((child, index) =>
        <div key={index} style={{ border: '1px solid #ccc', padding: '5px', margin: '5px' }} className="Row">
          {child}
        </div>
      )}
    </div>
  )
}

function MoreRows () {
  return (
    <>
      <p>This is the second item.</p>
      <p>This is the third item.</p>
    </>
  )
}

export default function App () {
  return (
   <>
    <RowList>
      <p>This is the first item.</p>
      <MoreRows />
    </RowList>

    <RowListWithChildren>
      <p>This is the first item.</p>
      <MoreRows />
    </RowListWithChildren>
    </>
  )
}

提示

此外注意虽然结构上是三个子元素但是由于 MoreRows 组件的存在,实际上是两个子元素,可以通过直接传入数组或者渲染属性处理此问题

延伸阅读

  • Children 官方文档对 Children 的详细讲解

setState() 是同步还是异步??

答案

参考 你真的理解setState吗?

注意下列概念只对 react17 之前有效,react18 及以后默认均采用 concurrent 模式,所以 setState 为异步

  1. setState 只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 等非 react 控制的流程中是同步。
  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/babel-standalone"></script>
<div id="root"></div>
<script type="text/babel">
   let { Component } = React;

   class Counter extends React.Component {
      constructor(props) {
         super(props);
         this.state = { count: 0 };
         this.refElem = React.createRef();
      }

      componentDidMount() {
         this.setState({ count: this.state.count + 1 });
         console.log("钩子中,setState 是异步,返回历史状态:", this.state.count);
         
         this.refElem.current.addEventListener(
            "click",
            this.nativeIncrement,
            false
         );

         setTimeout(() => {
            // 原生事件中 setState 是同步
            this.setState({ count: this.state.count + 1 });
            console.log("setTimeout,setState 是同步,返回最新状态:", this.state.count);
         }, 1000);
      }

      increment = () => {
         this.setState({ count: this.state.count + 1 });
         this.setState({ count: this.state.count + 1 });
         console.log("React 合成事件中,setState 是异步,返回历史状态:", this.state.count);
      };

      nativeIncrement = () => {
         this.setState({ count: this.state.count + 1 });
         this.setState({ count: this.state.count + 1 });
         console.log("原生事件中,setState 是同步,返回最新状态:", this.state.count);
      };

      fetchRemote = () => {
         fetch("https://jsonplaceholder.typicode.com/todos/1")
            .then((response) => response.json())
            .then((data) => {
               this.setState({ count: this.state.count + 1 });
               console.log("fetch setState 是同步,返回最新状态:", this.state.count);
            });
      };

      componentWillUnmount() {
         this.refElem.current.removeEventListener(
            "click",
            this.nativeIncrement,
            false
         );
      }

      render() {
         return (
            <div>
               {/* React 合成事件 setState 异步 */}
               <button onClick={this.increment}>react add</button>
               
               {/* 原生事件 setState 同步 */}
               <button ref={this.refElem}>dom add</button>
               {/* Fetch 事件 setState 同步 */}
               <button onClick={this.fetchRemote}>fetch add</button>
               <div>count: {this.state.count}</div>
            </div>
         );
      }
   }
   ReactDOM.render(<Counter />, document.getElementById("root"));
</script>

关联问题

延伸阅读

react 中的 key 有什么作用

答案

key 是 React 中用于标识列表项的特殊属性,通过 key 来比对列表元素的差异,实现高效的更新和重用。key 应该是每个列表项的唯一标识,通常使用列表项的 id 或其他唯一标识作为 key。

延伸阅读

react 中如何引入样式

答案
方式典型写法适用场景优缺点
外部 CSS 文件import './App.css'全局样式、简单项目全局污染、样式冲突
CSS Modulesimport styles from './App.module.css',<div className={styles.box}>中大型项目、样式隔离类名哈希隔离,易维护
内联样式<div style={{ color: 'red' }}>动态样式、简单场景无伪类/媒体查询,权重高
styled-components/emotion<code>const Btn = styled.buttoncolor: red;</code>组件级样式、主题切换支持 JS 逻辑、强大但需依赖
Tailwind CSS<div className="bg-blue-500">原子化、快速开发类名多,需配置
全局样式方案<style jsx global>、reset.css全局重置、第三方库需注意作用域

代码示例

// 1. 外部 CSS
import './App.css'
export default () => <div className="box">外部样式</div>

// 2. CSS Modules
import styles from './App.module.css'
export default () => <div className={styles.box}>模块样式</div>

// 3. 内联样式
export default () => <div style={{ color: 'red' }}>内联样式</div>

// 4. styled-components
import styled from 'styled-components'
const Btn = styled.button`color: red;`
export default () => <Btn>样式组件</Btn>
提示

推荐中大型项目优先用 CSS Modules 或 CSS-in-JS(如 styled-components),既能隔离作用域又便于维护。

延伸阅读

55%