MHUNTER.cn

October 23, 2025Last Updated: October 24, 2025

React 哲学

react13.1 min to read

对于初学者来说,直接阅读官方文档学习是一个糟糕的方式。

你能够快速掌握碎片化的语法,然而当你试图用 React 完成一个完整的项目时,你可能会无所适从,不知道这些碎片知识应该如何拼装在项目中。

你折腾很久,终于能够熟练使用 React 了,可是你依然无法产生得心应手的感觉。因此很多人会误以为 React 心智负担很重。

我从 2015 年底就开始学习并使用 React,并从 2017 开始教别人如何使用,经过长期的项目实战和教学摸索,我总结出来一套能够快速帮助学习者成为 React 高手的学习方式。

今天这篇文章,就是整个 React 学习核心中的核心。我将分为几个层面去介绍 React 的所有指导思想,如果完整吸收了这篇文章的内容,你将会轻松成为 React 高手。

前言

React 并非一个前端框架,而是一个 UI 库。他的优秀之处在于引导我们思考如何构建一个应用。因此,如果能够在学习之前脱离基础语法的束缚,站在更全面的角度去搞懂它的设计哲学,我们对于 React 的把控度将会更高,我们达到的高度也会更高。

我们将从以下几个阶段逐步介绍 React 的设计哲学。

一、组件化

组件化是前端独有的开发思维,是在模块化基础之上发展而来的更高效的开发方式,是实现所见即所得的优秀技术方案。

我们把页面上任何一个能够独立出来的部分称之为组件,组件的最小单位,是单个 HTML 元素。

事实上,DOM 元素也是组件化的范畴

例如一个完整的页面,我们可以称之为页面组件。通常一个项目能够拆分出来多个页面。

一个页面能够简单拆分为头部、内容、底部,这三部分也可以被看做是三个组件。

一个头部组件的拆分要视情况而定,例如下图,头部组件可以简单拆分为 logo 与 搜索框,这两个部分也可以称之为组件。

因此在前端开发中,组件化是一种先拆分,后合并的思维方式。我们能够利用这种思维,将一个项目的大问题,一层层拆分为单个组件的小问题。这是嵌套思维的运用。

HTML 标签其实是组件化思维的最早实践。例如,我们只需要在页面中,写入下面这段简单的代码,就能够在页面中渲染出一个按钮。只不过由于默认样式比较丑而极少单独运用于实践中。

CODE
<button>按钮</button>

可惜在 HTML 中,我们并没有任何一种方式能够如此轻松的得到一个自定义样式的组件。在最早的实现中,前端开发无法单独为按钮提供自定义样式,而必须与其他 CSS 代码写在一起。因此优秀的程序员会将组件化思路分别运用于 HTML 标签与 CSS 样式的编写,但这不是真正的组件化。

我们知道,一个按钮,通常由如下几部分共同组成

这些资源共同构成了一个完整的按钮。

最理想的方式,是把按钮当成一个独立的个体引入一次,即可完整呈现。

CODE
import Button from './components/Button'

但是在普通的开发结构中,我们做不到这样的便利。我们必须分别处理 HTML 标签、JavaScript 逻辑、CSS 样式,漏掉一个,就无法呈现完整的按钮。

得益于 webpack 的横空出世,组件化思维终于有了落地实现的技术基础。webpack 能够将任何资源文件处理成为 JavaScript 能够识别的模块,参与到 JavaScript 的逻辑中去。

例如,当我们要实现一个按钮组件时,我们可以这样去编写 JavaScript 逻辑

CODE
import logo from './logo.png'
import './index.css'

function Button() {
  return (
    <div className="container">
      <img src={logo} className="App-logo" alt="logo" />
      <button class="btn">自定义样式的组件</button>
    </div>
  )
}

这段代码比较诡异的地方在于,我们将图片资源与 CSS 文件当成了模块引入,并且还能够参与 JS 表达式的运算中去。webpack 能够帮助我们识别这样的语法,它让按钮的所有组成部分聚合在一起成为了一个整体。当我们在别的地方使用时,就只需要引入按钮组件即可。

在现有的客户端解决方案中,**HTML 标签是组件化最理想的代码表达方式。**因此,在 React 中,我们依然使用标签的形式去表达一个组件。

例如,一个项目,由多个页面组件组成,我们可以这样表达

通常,自定义组件,首字母大写

CODE
<div className="root">
  <Page1 />
  <Page2 />
  <Page3 />
</div>

一个页面,由头部、内容、底部组成

CODE
<div className="page1_container">
  <Header />
  <Content />
  <Footer />
</div>

一个头部组件,由 Logo 与 搜索框组成

CODE
<header>
  <img src={logo} alt="logo"/>
  <Search />
</header>

需要注意的是,在代码实现中,组件化主要体现在聚合上,我们会先编写一个个单独的组件,然后在更大的组件中将他们聚合在一起。但是,正确解决问题的思路是先思考拆分。我们需要先有一个大局观,首先着眼于整体页面,然后思考如何拆分并得到更小的组件。

组件化在文件结构上给我们的指引

这一部分知识非常关键。特别是对于有一定开发经验的朋友来说,它可能会彻底颠覆你的认知,并大幅度提高你的开发效率。

组件化的思维体现在组成部分的聚合。因此在文件结构上,组件化思维与后端项目常规 MVC 等分层思维差异很大,它的另一层意思,其实表达的是:凡是属于我的部分,都应该跟我放在一起。

在分层思维的影响下,大多数团队在前端项目中,组织代码结构的方式沿用了分层思维。

例如,我们的项目有 100 个页面,我们先不细分到组件,那么每个页面基本上都具备以下内容

在做项目组织的时候,我们一般都会如下这样处理

首先创建一个 pages 目录,将所有页面 UI 或者组件放到该文件夹中

  • 号表示文件夹,- 号表示文件
CODE
+ pages
  - Home
  - Profile
  - Detail
  - Show
  ...

然后创建一个 apis 目录,将所有的接口请求代码放在该目录中

CODE
+ apis
  - common.ts
  - home.ts
  - profile.ts
  - detail.ts
  - show.ts
  ...

再然后创建一个 models 目录,将所有的状态管理逻辑处理等模块放在该目录中

CODE
+ models
  - home.ts
  - profile.ts
  - detail.ts
  - show.ts

最后创建一个 styles 目录,将所有的 css 文件放在该目录

CODE
+ styles
  - common.css
  - home.css
  - profile.css
  - detail.css
  - show.css
  ...

于是,我们整个项目的代码层面,可能就会长这样

CODE
// 其他文件结构与脚手架有关,这里不做扩展
+ src
  + assets
  + commonets
  + pages
  + models
  + apis
  + styles

这是传统分层代码结构组织形式。那么它运用到前端,有什么问题呢?

在开发时,其实有一个痛点很多人都有,只是没有明确提出来。那就是当我们仅仅只是实现一个页面功能时,我们需要使用编辑器同时打开大量的代码文件,可能是 4、5 个,多一点的 10 多个都有。

因此前端的编辑器大多数都支持分屏功能。

那么在这种情况之下,当项目变得很大之后,文件与文件之间的位置就隔得很远。例如 pages/Homeapis/home.ts 他们虽然属于同一页面,但是在代码结构上却相隔得非常远。

当项目页面越多,我们要找到对应页面的组成部分,就越困难。在我们专注「摸鱼」的情况下,甚至会找错页面。

因此对于前端开发来说,这种分层的代码组织效率非常低下。

在组件化思维的指导下,我们应该采用新的组织方式。组件化的思维强调整体,强调聚合,也就是说,只要是一个组件的组件部分,我们应该尽量把他们放在一起。

例如,我们有一个 Home 页面,那么就应该这样去组织代码

CODE
+ Home
  - index.tsx
  - index.css
  - api.ts
  - model.ts

当然,如果页面比较复杂,可能还有更多的组成部分

CODE
+ Home
  + components  // 子组件
  + images      // 图片资源
  - index.tsx
  - api.ts
  - model.ts
  - interface.d.ts
  - config.ts
  - entry.ts  // 存放一些默认值、常量、隐射关系等
  - data.cvs  // 某种别的数据格式
  ...

在代码结构组织上,我们也把一个组件当成一个整体,只要是属于该组件的模块,基本上都放在一个文件夹里,对外来说,Home 文件夹,就代表了 Home 组件的全部。

这就是组件化思维指导下的代码组织方式。无论是做项目迁移,还是类似的页面复制功能,这都是最简单最高效的方式。

因此代码文件结构最终可能如下

CODE
+ src
  + components // 项目公共组件
  + pages // 所有的页面,将页面当成整体来看待
  + hooks // hooks 类工具方法
  + utils // 普通工具方法
  + api.ts // 少量的共有请求
  + router.ts // 处理路由配置

二、封装

所有高大上的开发思维,都是封装的运用。封装思维是所有程序员最底层的基本功。他决定了一个人水平的上限。

我们知道,HTML 标签其实就是组件化最早的运用,可是我们并不能利用起来,因为我们无法自定义组件。

React 提供了自定义组件的支持,这为我们组织代码带来了极大的想象空间。自定义组件,即为对标签的封装。

例如,一个列表可以用如下结构来实现

CODE
<ol>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
  <li></li>
</ol>

但是,当差不多一样格式的列表,在项目中出现多次时,如果不加以封装,我们就需要重复写多次这样的结构,明显这不是最理想的方案。

当有自定义组件的技术支持,我们就可以将该列表封装为一个自定义组件

CODE
<List />

这样,当它要出现在许多地方时,代码结构上,就会非常简单。

与其他封装思路一样,封装的核心应用,在于我们是否能够准确的区分出来共性与差异。例如对于函数来说,共性的逻辑我们封装到函数代码里,差异的内容,通过参数传入。

因此,我最终要将其封装为一个
    这样的格式参与运行。该列表有 4 个子项,经过分析我们知道,每一个子项的结构,样式等都是一样的,而图标、标题等文字内容不同。因此,我们可以将这四个子项封装为一个组件,在运用时,通过不同的参数传入来分别表示这四个子项。

    于是 List 组件可以由如下方式组成

    CODE
    <ul className="list-wrapper">
      <Item icon={item1.icon} title={item1.title} numer={item1.number} size={item1.size} />
      <Item icon={item2.icon} title={item2.title} numer={item2.number} size={item2.size} />
      <Item icon={item3.icon} title={item3.title} numer={item3.number} size={item3.size} />
      <Item icon={item4.icon} title={item4.title} numer={item4.number} size={item4.size} />
    </ul>

    再次利用封装思维,上面的代码可简化如下:

    CODE
    // 首先抽取不同的部分组合成为一个数组,数据不同,标签相同
    const data = [item1, item2, item3, item4]
    
    // 然后利用遍历的方式,将数组的值一次赋值给每一个 Item 组件
    <ul className="list-wrapper">
      {data.map(item => (
        <Item icon={item.icon} title={item.title} numer={item.number} size={item.size} />
      ))}
    </ul>

    每一个字段,都通过标签的属性 props 传入。我们在声明自定义组件 Item 时,只需要能够读取到这些从外部传入的属性值,就可以完整的渲染出来 4 个不同的子项。

    React 提供了读取 props 的语法。

    因为在 React 中,是将标签语言作为 JavaScript 的逻辑表达式在使用,因此为了避免与声明类的关键字冲突,标签的 class 名都需要修改为 className

    子组件 Item 的封装代码如下:

    CODE
    // 函数的第一个参数,即为所有传入的 prop 组成的对象
    function Item(props) {
      const {icon, title, number, size} = props
    
      return (
        <div className="item-wapper">
          <img className="left" src={icon} alt="icon" />
          <div className="middle">
            <div className="title">{title}</div>
            <div className="number">{number}</div>
          </div>
          <div className="right size">{`${size}GB`}</div>
        </div>
      )
    }

    于是,列表这一部分功能,我们就可以单独拿出来开发。

    CODE
    // 引入 Item 组件
    import Item from './Item'
    import icon1 from 'images/icon1.png'
    import icon2 from 'images/icon2.png'
    import icon3 from 'images/icon3.png'
    import icon4 from 'images/icon4.png'
    import './index.css'
    
    export default function List() {
      const data = [{
        icon: icon1,
        title: 'Documents Files',
        number: 1328,
        size: 1.3
      }, {
        icon: icon2,
        title: 'Media Files',
        number: 1328,
        size: 15.1
      }, {
        icon: icon3,
        title: 'Other Files',
        number: 1328,
        size: 12.7
      }, {
        icon: icon4,
        title: 'Unknown',
        number: 428,
        size: 1.3
      }]
    
      return (
        <ul className="list-wrapper">
          {data.map(item => (
            <Item icon={item.icon} title={item.title} numer={item.number} size={item.size} />
          ))}
        </ul>
      )
    }

    值得注意的是,组件化封装思维的核心依然在找出相同的逻辑部分,与有差异的部分,在某些情况下,这很难做到,需要大家在实践中多思考,多积累,多交流。

    在封装思维的驱动下,一部分数据被单独抽离出来。如果标签表达的是 UI,那么这个时候,我们就把传统的组件,在内部拆分成为了数据层与 UI 层。我们发现,数据层与 UI 层在某种意义上是一一对应的关系。理解这一点,对未来我们去探索最佳实践时,有极大的帮助。

    因此,追求 UI 层的所见即所得,与追求数据层的所见即所得,成为组件化的最高评判标准。

    三、数据驱动 UI

    交互是前端开发无法避免的研究课题。通过《JavaScript 核心进阶》的学习我们知道,一个网页的运行是由多个线程共同协作完成。

    当页面通过 GUI 线程首次渲染完成之后,页面并非永远一成不变。不同的线程活动,需要页面给予相应的逻辑反馈。

    例如,当我们点击按钮,页面出现一个新的弹窗,弹窗上通常会有关闭按钮,点击该按钮,弹窗关闭。这是最常见的交互之一。

    点击行为通过 I/O 线程告知页面,假如弹窗组件的标签已经提前写在了 HTML 中,使用 display: none 隐藏,传统方式下,要实现这样的效果,页面逻辑大概会有如下步骤

    CODE
    var dialog = document.getElementById('dialog')
    dialog.className = 'show'

    传统方式下,当交互逐渐变得复杂,我们需要获取大量的元素对象,然后通过对应的 api 去操作他们。这会让项目变得越来越难以维护。React 提供了一种新的方式:数据驱动视图,去解决这样的复杂度。

    在上面封装自定义组件的案例中,我们区分了数据层与 UI 层。如果数据发生了变化,数据会驱动 UI 组件重新渲染,从而让 UI 也发生一致的变化。这就是数据驱动视图

    数据驱动的开发思维,引导我们关注数据的所见即所得,仅仅通过操作数据的思维去改变 UI 渲染结果。React 内部组件提供了_state_ 的方式,用于存放能够驱动视图的数据

    弹窗案例中,我们可以将弹窗的显示与隐藏的行为,在数据层抽象为一个具体的值:show。当我们改变 show = true 时,弹窗显示,当我们改变数据 show = false 时,弹窗隐藏。

    因此,这个逻辑我们可以封装如下即可:

    CODE
    const App = () => {
      // 默认值为 false, 弹窗隐藏
      const [show, setShow] = useState(false)
    
      return (
        <div className="root">
          <button onClick={() => setShow(true)}>显示对话弹窗</button>
          <Dialog show={show} onClose={() => setShow(false)} />
        </div>
      )
    }

    我们将数据 show 通过 React 的语法 useState 存放在 state 中,这些数据的变化会驱动视图发生变化。因此,当我们使用 _setShow(true) _ 和 _setShow(false) _ 改变 show 的值时,弹窗组件能够相应反馈显示和隐藏。

    当 state 发生改变时,组件函数 App 会重新执行

    结合上文中我们提到的对属性 props 的处理,自定义组件 Dialog 的逻辑可以很好实现,代码大概如下:

    CODE
    function Dialog({show, onClose}) {
      return (
        <div className={`dialog ${show ? 'show' : 'hide'}`}>
          <button onClick={onclose}>关闭按钮</button>
          其他内部内容
        </div>
      )
    }

    show 的变化,对应控制 div className 的变化让弹窗显示或者隐藏。关闭按钮 onClick 执行的回调函数由传入的属性 onClose 来决定。结合上例我们知道,onClose 的逻辑是 setShow(false),因此,点击该按钮时,show 改变为 false ,弹窗隐藏。

    数据驱动视图的开发思维下,我们就不再去思考如何获得 DOM 元素的引用来完成对应的逻辑,而是学会将 UI 行为抽象成为数据,并通过改变数据的方式完成交互需求。我们只需要把最多的精力放在数据如何发生变化即可。通常情况下,通过 http 线程请求服务端数据,通过 I/O 线程监听用户输入信息,通过定时器线程执行对应的逻辑等方式都会导致数据发生变化,这也是我们后续学习,需要关注的重点。

    这里需要注意的是,使用 state 存放的数据有自身的特性,那就是能够驱动对应的 UI 发生变化,我们在开发中,还会用到许多跟 UI 变化没有关系的数据,这个时候,就建议不要使用 state 来存放。总之,state 数据尽量与 UI 一一对应。

    而掌握「数据驱动 UI 」 的核心哲学,需要反过来解读。也就是说,什么时候我们需要从 UI 中抽离数据出来。

    那就是:会在交互中变化的 UI,需要抽离出数据,并用 state 存放。

    例如列表组件,初始化列表为空,请求一次接口之后,列表有了数据,那么列表的内容是变化,抽离出来的数据就是一个数组

    CODE
    function App() { 
      const [list, setList] = useState([])
    
      return (
        <List data={list} renderItem={(item) => <Item ={item} />} />
      )
    }

    建议大家可以专门做一做这一方面的训练,去更多的思考一下不同组件的交互提炼出来的数据应该长什么样。例如单选,多选,tab 切换等等,也可以在群里互相交流。

    如何拆分组件,最小颗粒度到什么程度,是许多同学使用 React 的难点。然而这实际上是封装思维的进阶运用,因此考究的是你封装能力的火候。单独的文章无法迅速提高你的封装底层能力,它需要大量的思考与练习,但是我可以提供三个原则帮助大家尽可能的快速提高自己的封装能力。

    1. 明确什么时候需要封装

    封装的目的是为了简化代码。因此这两种情况下,我们都应该考虑封装代码。

    首先,当代码量过大,造成阅读困难时。

    其次,当相同或者类似的代码逻辑,出现多次时。

    基于这两个前提条件,我们就可以做出如下决策。

    2. 人为约定组件的代码规模

    为了让代码保持简洁性,每一个组件的代码行数,应该尽可能的保持在一定的数量之内。我们可以约定为 200 行,或者 150 行,这根据团队的具体情况来定。一旦超出我们约定的行数,那么就表示我们需要将组件进行拆分了。这是一个非常棒的判断标准。

    实际情况是,在没有正确的引导下,许多同学悲剧的将一个组件写出了几千行,甚至几万行代码,这维护成本杠杆的,别说没有,没准就是你

    3. 分析相同/类似 UI 的出现场景

    例如,当类似的列表出现多次,他们只是数据不同,但是数据格式相同,我们就可以封装成为一个 List 组件。这里的核心在于,找出组件的相同之处与差异之处。

    4. 关注数据

    数据作为影响组件的重要组成部分,是最容易被忽略的存在。也是最考验开发者思维的部分。许多项目为了避免数据对组件造成干扰,就使用了全局的状态管理工具 redux、mobx等等,将所有的数据都存放在全局的 store 中。

    这是一种解决方案。但是过于简单粗暴,限制了开发者的创作能力。在大型项目中,甚至存在内存占用过多的隐患。因此,精准分析数据是 React 知命境的追求目标。

    找准数据归属于谁,在实际开发中,最麻烦的地方在于许多数据,是由组件之间相互共享的。因此,解决这部分问题,跟理解闭包的场景有莫大的关系,关键词就在于:局部数据共享。

    当组件之间共享数据,我们就应该把数据存放在他们最近的父组件之中。

    反过来说,如果数据不存在共享,那么数据就应该属于对应组件私有。

    当我们想要开灯时,只需要按下开关,灯自然会亮。关闭时同理。

    这和数据驱动视图的逻辑如出一辙。当我们想要改变视图时,只需要改变某一个数据,React 自然会帮助我们改变视图。数据驱动视图引导我们更关注数据,而开关思维,则引导我们更关注操作数据的方式,例如上例中的 setShow(true)

    开关思维是数据驱动视图的进一步提炼,它能有效的帮助我们找到最佳实践:即改变数据的方式越简单越好,最好只有一行代码。

    此时,我们首先将数据抽象出来,大概有

    CODE
    // 下拉刷新页面头部 loading 的控制数据
    const [refreshing, setRefreshing] = useState(false)
    
    //  上拉加载页面底部 loading 的控制数据
    const [incresing, setIncresing] = useState(false)
    
    // 列表内容的数据
    const [list, setList] = useState([])

    在开关思维中,数据是电流,开关就是控制电流的接口。当我们把数据抽象好了之后,开关的接口如何提供,是我们要去思考的问题。这样的思考,会引导我们创建更为简单的组件。

    我们在设计这个组件时,可以将两个 loading 与 列表分开来思考,也可以将 loading 与列表看成一个整体。那么哪一种方式是最简单的呢?

    从交互逻辑上来说,列表的变化,与 loading 的变化息息相关。当我们下拉刷新时,最终的目的是要通过 http 去服务器请求最新的数据,以改变列表内容。因此,下拉刷新之后,loading 开始出现,loading 的出现过程中,其实就是 http 请求接口的过程,当 http 请求完毕时,loading 就应该消失,与此同时,列表内容也发生改变。

    下拉刷新 -> loading 出现 并且 http 开始请求 -> 请求成功 -> loading 消失 并且列表视图更新

    loading 的变化与列表的变化在时间节点上息息相关,因此,我们应该将列表与 loading 处理成为一个整体放在一个列表组件中。那么,该列表组件的参数就能够很自然的想到应该如下:

    CODE
    // 假设列表子项固定
    <PaginationList 
      list={list} 
      refreshing={refreshing} 
      incresing={incresing} 
    />

    当然,我们还需要提供下拉刷新和上拉加载的两个接口,用于在对应的行为之下,做出改变。

    CODE
    <PaginationList 
      list={list} 
      refreshing={refreshing} 
      incresing={incresing} 
      onRefresh={?}
      onIncrese={?}
    />

    那么,这个接口行为应该如何提供呢?

    常规思维下,onRefresh 表示触发下拉刷新动作,那么根据上面的逻辑,我们应该:

    CODE
    function onRefresh() {
      // 出现 loading
      setRefreshing(true)
    
      // 并且请求接口
      http.get(api).then(res => {
        setList(res.data)
        setRefreshing(false)
      })
    }
    
    function onIncrese() {
      setIncresing(true)
      http.get(api).then(res => {
        setList([list, ...res.data])
        setIncresing(false)
      })
    }

    而开关思维下,我们希望接口行为更简单。因此我们要结合这里的实际情况将交互逻辑进行一些合理的演变。

    下拉刷新 -> refreshing 改为 true -> 页面开始请求数据 -> 请求成功 -> refreshing 改为 false

    此时,当页面刷新时,我们只需要修改一个状态为 true,后续的逻辑,则封装起来。所以,这个时候,我们需要一种方式去监听到 refreshing 的变化,React hooks 中提供了一个 api 能帮助我们做到这个事情:useEffect

    CODE
    useEffect(() => {
      if (refreshing) {
        http.get(api).then(res => {
          setList(res.data)
          setRefreshing(false)
        })
      }
    }, [refreshing]) 
    // refreshing 发生变化时,useEffect 回调函数必定执行
    
    // 开关回调就变得异常简单
    function onRefresh() {
      setRefreshing(true)
    }

    此时,我们发现,开关回调就变得非常简单。

    这就是自定义 hooks。

    自定义 hooks 能够帮助我们封装请求逻辑,最后组件的会变成如下的样子。

    CODE
    function App() {
      const {refreshing, incresing, list, setRefreshing, setIncresing} = usePagination(api)
    
      return (
        <PaginationList 
          list={list}
          refreshing={refreshing}
          incresing={incresing}
          onRefresh={() => setRefreshing(true)}
          onIncrese={() => setIncresing(true)}
        />
      )
    }

    我们发现,当我们将工具方法 usePagination 封装好后,我们在编写新的页面时,只需要调用该方法传入请求接口与参数,得到列表组件需要的数据与开关即可。

    开关思维是建立在数据驱动 UI 的基础之上。当 UI 操作变得复杂,我们使用数据驱动 UI 的思维来简化开发代码。随着项目的规模和场景日益复杂,当操作数据也变得复杂时,我们就需要想到开关思维:使用一个数据,去驱动多个数据,再用多个数据,去驱动 UI。

    这就是开关思维的核心。也是自定义 hooks 的终极奥义。开关思维能极大减少前端开发的工作量。

    学会 React 语法并非难事,理解 React 哲学,才是重中之重,这篇文章的内容能够让你在 React 的学习过程中少走许多弯路,所以建议大家反复阅读直到彻底掌握为止。

    Loading comments...