You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React真正解决的核心问题是什么?起源场景深度解析

React诞生要解决的本质问题:从Facebook自动补全场景的真实开发困境说起

很多人对React核心定位的疑惑,本质是没亲历过“手动同步多源异步数据和DOM”复杂度爆炸的阶段——总觉得不就是几个接口拿数据,拿到了往DOM节点里追加内容就行,犯得上搞个框架吗?
你要是真做过迭代半年以上的动态交互页面,就知道这个想法有多天真。

先还原Jordan Walke当年碰到的真实业务场景

他当时在Facebook要做的搜索自动补全框,根本不是单数据源的简单下拉列表,补全结果同时来自好几个完全独立的后端接口:

  • 用户搜索历史记录接口
  • 好友关系匹配接口
  • 所在小组内容匹配接口
  • 公共主页匹配接口
  • 商业广告推送接口

这些接口没有统一的网关层做聚合,返回时间完全不可控:快的100ms就能返回,网络差的时候可能要等1s以上,甚至直接请求失败。再加上用户输入是连续的,往往上一个关键词的请求还没回来,用户已经输入了下一个字符,发出去了新的请求。

纯手动操作DOM的写法,为什么必然走向屎山

很多人脑子里的简单实现是这样的:

const suggestContainer = document.getElementById('suggest-box')
// 好友接口回来就往容器里加好友项
fetch(`/api/friends?q=${keyword}`).then(res => res.json()).then(list => {
  list.forEach(item => {
    const el = document.createElement('div')
    el.innerText = item.name
    suggestContainer.appendChild(el)
  })
})
// 小组接口回来就追加小组项
fetch(`/api/groups?q=${keyword}`).then(res => res.json()).then(list => {
  list.forEach(item => {
    const el = document.createElement('div')
    el.innerText = item.name
    suggestContainer.appendChild(el)
  })
})

这种写法demo跑起来没问题,一上生产、一接真实需求马上崩,你要处理的问题多到数不过来:

  • 结果要按固定优先级排序:历史记录在最上面,然后是好友,然后是小组,然后是公共主页,最后是广告。你不能等所有接口都返回了再一起渲染(那样用户等待时间太长体验差),所以每一个接口返回的时候,你都得先遍历一遍当前容器里已经插了哪些类型的节点,算好当前这批内容该插在第几个子元素的位置,插错了排序就乱。
  • 关键词更新的时候要清理旧内容,但又不能全删了重建——全删全插DOM性能太差,输入快了会卡顿。你得给每个创建出来的DOM节点打自定义标记,记清楚它对应哪个关键词、哪个内容id、哪个分类,清理的时候只删掉不属于当前关键词的节点,能复用的就留着改改文本就行。
  • 要处理异步竞态:旧关键词发出去的请求,很可能比新关键词的请求晚回来,这时候你不能把旧结果插到当前的列表里,所以每个请求回调里都得加判断,确认当前返回的结果是用户最新输入的关键词对应的,才能操作DOM。
  • 需求只会越堆越多:实名认证的好友要加蓝V标,特别关注的好友名字要标红,用户点过关闭的广告24小时内不能再出现,所有分类都没结果的时候要显示“暂无匹配内容”的占位,某个接口加载失败要在对应分类位置显示“加载失败点此重试”。

等你把这些逻辑都写完,代码里已经到处散着DOM查询、位置计算、节点增删改、标记判断的逻辑,每加一个新需求,你都要去好几个接口的回调里改对应的DOM操作。出bug的时候更崩溃——比如“好友接口返回慢了、广告接口先返回、用户刚好在这时候删了两个字符、之前还点过某条广告的关闭”,这种极端时序下出现的排序错乱、旧内容残留问题,你调一下午都不一定能复现。

别觉得这是程序员代码写得差才会乱,只要你选择手动维护数据和DOM的同步关系,这部分复杂度就是客观存在的,不管你怎么封装工具函数、怎么设计代码结构,只要业务一直迭代,代码迟早会变成没人敢动的屎山。

React的核心思路,就是把这部分无意义的复杂度彻底接走

很多人背React的特性,什么虚拟DOM、组件化、声明式编程,这些都是实现手段,不是目的。它从诞生第一天要解决的问题就非常明确:你再也不用手动管DOM该怎么增删改、该插在哪个位置、哪些节点可以复用,你只要做两件事:

  1. 把所有影响UI的状态(接口返回结果、当前关键词、加载状态、错误状态、用户的广告关闭记录等等)存在一起
  2. 写死一个规则:给定确定的状态,UI应该长什么样——比如历史记录排第一、蓝V用户加图标、关注的人标红、没数据显示占位、报错显示重试按钮

剩下所有和DOM操作相关的脏活累活,全交给React处理:不管哪个接口先返回后返回,你只要把新结果更新到状态里,React会自动对比新旧UI的差异,该插的节点算好位置插,该复用的节点直接复用,该删的节点删掉,最终渲染出来的结果一定和你定义的规则完全一致。

同样的自动补全场景,用React写的逻辑是这样的:

function SearchSuggest({ keyword }) {
  // 所有分类的结果统一存在状态里
  const [resultList, setResultList] = useState({
    history: [],
    friends: [],
    groups: [],
    pages: [],
    ads: []
  })
  const [loading, setLoading] = useState(false)
  const [errorMap, setErrorMap] = useState({})

  useEffect(() => {
    setLoading(true)
    // 标记当前请求是否是最新的,处理竞态
    let isLatestRequest = true
    // 每个接口回来只做一件事:更新对应的数据,完全不碰DOM
    fetchHistory(keyword)
      .then(data => isLatestRequest && setResultList(prev => ({...prev, history: data})))
      .catch(err => isLatestRequest && setErrorMap(prev => ({...prev, history: err})))
    fetchFriends(keyword)
      .then(data => isLatestRequest && setResultList(prev => ({...prev, friends: data})))
      .catch(err => isLatestRequest && setErrorMap(prev => ({...prev, friends: err})))
    fetchGroups(keyword)
      .then(data => isLatestRequest && setResultList(prev => ({...prev, groups: data})))
      .catch(err => isLatestRequest && setErrorMap(prev => ({...prev, groups: err})))
    // 其他接口逻辑同理

    return () => { isLatestRequest = false }
  }, [keyword])

  // 下面纯根据状态渲染UI,没有任何手动DOM操作
  return (
    <div className="suggest-box">
      <Section title="搜索历史" items={resultList.history} error={errorMap.history} />
      <Section title="好友" items={resultList.friends} error={errorMap.friends}>
        {item => (
          <div className="friend-item">
            {item.isVerified && <VerifiedIcon />}
            <span className={item.isFollowed ? 'text-red' : ''}>{item.name}</span>
          </div>
        )}
      </Section>
      <Section title="小组" items={resultList.groups} error={errorMap.groups} />
      <Section title="公共主页" items={resultList.pages} error={errorMap.pages} />
      <Section title="推荐广告" items={resultList.ads.filter(ad => !ad.closed)} />
      {!loading && isAllEmpty(resultList) && <div className="empty">暂无匹配内容</div>}
    </div>
  )
}

这种写法下,你根本不用关心异步返回顺序、不用算DOM插入位置、不用管节点复用,只要你的状态是对的,渲染结果就一定是对的。加新需求的时候,你只要改状态和UI的映射规则就行,不用去散落在各处的DOM操作逻辑里找补,维护成本直接降了一个量级。

说白了,React解决的本质问题,就是把“数据变化时手动同步DOM”这件事从开发者的工作里彻底拿掉,让UI成为数据的可预测映射,从根源上解决大型动态应用因为状态和UI不同步导致的各种维护难题。


内容的提问来源于stack exchange,提问作者Code Eagle

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 15:09:18