文章

API设计哲学对比深度解析

深入对比Vue模板语法与React JSX、选项式API与函数式API的设计哲学差异,从语言特性、心智模型到工程实践的全面剖析

API设计哲学对比深度解析

一句话概括

Vue的模板语法与React的JSX、选项式API与函数式API之间的差异,本质上是”HTML声明式增强”与”JavaScript表达力最大化”两种设计哲学的碰撞,前者追求直观和约束,后者追求灵活和统一。

背景与意义

选择前端框架不仅是在选择技术工具,更是在选择一种编程思维模式。Vue和React在这方面的分歧如此之大,以至于”学Vue容易学React难”成为社区共识,而”Vue适合模板开发,React适合逻辑复杂的场景”也成为常见的经验之谈。

这种差异不源于技术实现,而源于框架的设计哲学起点

  • Vue认为HTML应该是起点——在HTML的基础上做增强,保持模板的自然性和直观性
  • React认为JavaScript才是起点——组件就是JavaScript函数,一切皆JS

这种哲学分歧贯穿了整个API设计:从模板语法到组件定义,从状态管理到生命周期理解。理解这些差异的根源,比记住语法区别更有价值——它能帮助开发者在合适的场景选择合适的工具。

概念与定义

模板语法(Vue)

Vue的模板是基于HTML的声明式语法,在HTML标签的基础上增加了v-ifv-forv-bind等指令:

1
2
3
4
5
6
<template>
  <div v-if="visible" :class="{ active: isActive }">
    <p>{{ message }}</p>
    <button @click="handleClick">点击</button>
  </div>
</template>

JSX语法(React)

JSX是JavaScript的语法扩展,在JavaScript中直接写类似HTML的标记:

1
2
3
4
5
6
7
8
function Component({ visible, isActive, message }) {
  return (
    <div className={`wrapper ${isActive ? 'active' : ''}`}>
      {visible && <p>{message}</p>}
      <button onClick={handleClick}>点击</button>
    </div>
  )
}

选项式API(Options API)

Vue2/Vue3支持的经典组件定义方式,将组件的数据、方法、生命周期等按”选项”划分:

1
2
3
4
5
6
7
export default {
  data() { return { count: 0 } },
  methods: { increment() { this.count++ } },
  computed: { double() { return this.count * 2 } },
  watch: { count(newVal) { console.log(newVal) } },
  mounted() { console.log('mounted') },
}

函数式API(Composition API)

Vue3引入的、以函数组合为核心的组件定义方式:

1
2
3
4
5
6
7
8
9
10
export default {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)
    const increment = () => count.value++
    watch(count, (newVal) => console.log(newVal))
    onMounted(() => console.log('mounted'))
    return { count, double, increment }
  }
}

这实质上将Vue的组件定义向React Hooks看齐。

最小示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- Vue: 模板语法 + 选项式API -->
<template>
  <div>
    <input v-model="text" placeholder="输入内容" />
    <p>你输入了: {{ text }}</p>
    <p>字符数: {{ charCount }}</p>
    <button @click="clear">清空</button>
  </div>
</template>

<script>
export default {
  data() { return { text: '' } },
  computed: { charCount() { return this.text.length } },
  methods: { clear() { this.text = '' } },
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// React: JSX + Hooks
function CharacterCounter() {
  const [text, setText] = useState('')
  const charCount = text.length

  return (
    <div>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
        placeholder="输入内容"
      />
      <p>你输入了: {text}</p>
      <p>字符数: {charCount}</p>
      <button onClick={() => setText('')}>清空</button>
    </div>
  )
}

两个例子功能完全相同,但思维路径截然不同:Vue通过指令描述HTML的行为,React通过JavaScript控制渲染输出。

核心知识点拆解

1. JSX vs 模板:JavaScript表达力vs可读性约束

JSX的优势(JavaScript表达力最大化)

JSX本质上就是JavaScript函数调用,因此可以无缝使用JavaScript的所有能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 数组map → 列表渲染
{items.map(item => (
  <Item key={item.id} data={item} />
))}

// 三元表达式 → 条件渲染
{user ? <Profile user={user} /> : <LoginPrompt />}

// 短路运算 → 快捷条件
{unreadCount > 0 && <Badge>{unreadCount}</Badge>}

// 逻辑运算 → 复杂条件组合
{isVisible && (isAdmin || isOwner) && <AdminPanel />}

// 解构、展开、箭头函数...
// 所有JavaScript的能力都可用

模板语法的优势(可读性约束与编译优化)

Vue模板不是JavaScript,它是一套带有限定的声明式语法。这种限定看似是一种限制,实际上带来了两个关键收益:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 1. 可读性强:自然语言化的指令 -->
<template>
  <div>
    <ul v-if="items.length">
      <li v-for="item in items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
    <p v-else>暂无数据</p>
  </div>
</template>

<!-- 2. 编译时优化:模板是静态的,编译器可以做深度优化 -->
<!-- 比如Block Tree、patchFlags、静态提升等 -->

局限与变通

模板的局限在于无法直接使用JavaScript的高级特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 模板中无法这样用: -->
<!-- ❌ 模板不支持 ?? 运算符 -->
<!-- <div>{{ user ?? '游客' }}</div> -->

<!-- ✅ 需要在计算属性中处理 -->
<script setup>
const displayName = computed(() => user.value ?? '游客')
</script>

<!-- 模板中条件渲染只能用v-if/v-else链 -->
<!-- ❌ 无法用 && -->
<!-- <div v-if="isLoggedIn && showName">{{ name }}</div> -->
<!-- ✅ 需要在计算属性或直接在模板中用包一层 -->

Vue3的<script setup> + 模板的演进方向,是在保持模板可读性的前提下逐步增强模板的表达力。

2. Composition API vs Hooks:函数组合的不同实现

Vue3的Composition API和React Hooks都是”函数组合”的产物,但实现机制有根本差异:

React Hooks(基于链表+调用顺序)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// React Hooks依赖于稳定的调用顺序
function MyComponent() {
  const [count, setCount] = useState(0)      // hook #1
  const [name, setName] = useState('')       // hook #2

  useEffect(() => {                          // hook #3
    document.title = `${name} clicked ${count} times`
  }, [count, name])

  // 注意:如果下面的条件导致hook#3不执行
  // 所有后续hook的索引都会错位!
  if (count > 5) {
    useEffect(() => { /* 这是hook#4 */ })    // 条件中的hook → ❌ 错误!
  }

  // React规定:hook必须在顶层调用,不能放在条件或循环中
}

React Hooks的”调用顺序依赖”是其最大的使用约束。每次渲染都必须以完全相同的顺序调用完全相同的hooks,否则会导致严重的bug。

Vue Composition API(基于响应式系统)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Vue的setup函数中没有调用顺序限制
export default {
  setup() {
    const count = ref(0)
    const name = ref('')

    watchEffect(() => {
      document.title = `${name.value} clicked ${count.value} times`
    })

    // ✅ 条件中的watch也完全OK!
    if (count.value > 5) {
      watch(name, (newVal) => {
        console.log('name changed:', newVal)
      })
    }

    return { count, name }
  }
}

为什么没有顺序限制?因为Vue的响应式系统基于Proxy的对象属性访问,而不是基于函数调用顺序。每个ref()返回的是一个响应式对象,watch()订阅的是响应式对象的变化——这天然地不依赖于调用顺序。

维度React HooksVue Composition API
依赖基础函数调用顺序(链表索引)响应式对象属性访问
条件调用❌ 禁止✅ 允许
循环调用❌ 禁止✅ 允许
闭包陷阱需要处理(useRef)无(始终读取最新值)
垃圾回收自动(配合deps)需要手动stop

3. 选项式API vs 函数式API:组织方式的选择

虽然在Vue3中推荐使用Composition API,但选项式API仍然完全可用。两者的选择本质上是“按选项划分”还是”按逻辑关注点划分”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 选项式API:按选项类型组织
export default {
  data() {                                // 数据
    return { searchQuery: '', results: [], loading: false }
  },
  watch: {                                // 监听
    searchQuery: 'debouncedSearch',
  },
  computed: {                             // 计算
    hasResults() { return this.results.length > 0 },
  },
  methods: {                              // 方法
    debouncedSearch() { /* ... */ },
    fetchResults() { /* ... */ },
  },
  mounted() {                             // 挂载
    this.fetchResults()
  },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 函数式API (Composition API):按逻辑关注点组织
export default {
  setup() {
    // 搜索逻辑(包含数据、方法、生命周期)
    const searchQuery = ref('')
    const results = ref([])
    const loading = ref(false)

    const debouncedSearch = useDebouncedWatch(searchQuery, 300)
    const fetchResults = async () => {
      loading.value = true
      results.value = await api.search(searchQuery.value)
      loading.value = false
    }

    watch(debouncedSearch, fetchResults)
    onMounted(fetchResults)

    // 派生状态
    const hasResults = computed(() => results.value.length > 0)

    return { searchQuery, results, loading, hasResults, fetchResults }
  },
}

选项式API适合简单组件,逻辑按选项类别组织直观易懂。函数式API适合复杂组件,可以将关联逻辑抽取为独立的组合函数(Composable):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 将搜索逻辑抽取为可复用的组合函数
function useSearch(apiEndpoint) {
  const query = ref('')
  const results = ref([])
  const loading = ref(false)
  const error = ref(null)

  const debouncedQuery = useDebouncedWatch(query, 300)

  async function search() {
    loading.value = true
    error.value = null
    try {
      results.value = await fetch(apiEndpoint + '?q=' + query.value).then(r => r.json())
    } catch (e) {
      error.value = e.message
    } finally {
      loading.value = false
    }
  }

  watch(debouncedQuery, search)

  return { query, results, loading, error, search }
}

// 在组件中使用
export default {
  setup() {
    const { query, results, loading, error, search } = useSearch('/api/users')
    return { query, results, loading, error, search }
  },
}

类似地,React的自定义Hook也实现了完全相同的逻辑复用能力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function useSearch<T>(apiEndpoint: string) {
  const [query, setQuery] = useState('')
  const [results, setResults] = useState<T[]>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const debouncedQuery = useDebounce(query, 300)

  useEffect(() => {
    if (!debouncedQuery) return
    const controller = new AbortController()

    setLoading(true)
    fetch(apiEndpoint + '?q=' + debouncedQuery, { signal: controller.signal })
      .then(r => r.json())
      .then(data => { setResults(data); setError(null) })
      .catch(e => { if (e.name !== 'AbortError') setError(e.message) })
      .finally(() => setLoading(false))

    return () => controller.abort()
  }, [debouncedQuery, apiEndpoint])

  return { query, setQuery, results, loading, error }
}

实战案例:搜索表单组件对比

Vue3版本(Composition API + 模板)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<template>
  <div class="search-form">
    <input
      v-model="query"
      type="search"
      :placeholder="placeholder"
      @keydown.enter="handleSearch"
    />
    <select v-model="category">
      <option value="">全部分类</option>
      <option v-for="cat in categories" :key="cat" :value="cat">
        {{ cat }}
      </option>
    </select>
    <button :disabled="loading" @click="handleSearch">
      {{ loading ? '搜索中...' : '搜索' }}
    </button>
    <p v-if="error" class="error">{{ error }}</p>
    <div v-if="loading" class="spinner" />
    <ul v-else-if="results.length">
      <li v-for="item in results" :key="item.id">
        <a :href="item.url">{{ item.title }}</a>
        <span class="badge">{{ item.category }}</span>
      </li>
    </ul>
    <p v-else-if="query && !loading">没有找到结果</p>
  </div>
</template>

<script setup>
import { ref, watch, computed } from 'vue'

const props = defineProps({
  placeholder: { type: String, default: '搜索...' },
})

const query = ref('')
const category = ref('')
const results = ref([])
const loading = ref(false)
const error = ref(null)

const categories = computed(() => {
  // 从results中提取所有出现过的分类
  return [...new Set(results.value.map(r => r.category))]
})

watch(query, (newVal) => {
  if (newVal.length >= 2) {
    debouncedSearch()
  }
})

let timeoutId = null
function debouncedSearch() {
  clearTimeout(timeoutId)
  timeoutId = setTimeout(fetchResults, 300)
}

async function fetchResults() {
  loading.value = true
  error.value = null
  try {
    const params = new URLSearchParams({ q: query.value })
    if (category.value) params.set('cat', category.value)
    const res = await fetch(`/api/search?${params}`)
    if (!res.ok) throw new Error(res.statusText)
    results.value = await res.json()
  } catch (e) {
    error.value = e.message
  } finally {
    loading.value = false
  }
}

function handleSearch() {
  fetchResults()
}
</script>

React版本(Hooks + JSX)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'

type SearchResult = {
  id: string
  title: string
  url: string
  category: string
}

type SearchFormProps = {
  placeholder?: string
}

export default function SearchForm({ placeholder = '搜索...' }: SearchFormProps) {
  const [query, setQuery] = useState('')
  const [category, setCategory] = useState('')
  const [results, setResults] = useState<SearchResult[]>([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)

  const debounceRef = useRef<ReturnType<typeof setTimeout>>()

  const categories = useMemo(
    () => [...new Set(results.map(r => r.category))],
    [results]
  )

  const fetchResults = useCallback(async (q: string, cat: string) => {
    setLoading(true)
    setError(null)
    try {
      const params = new URLSearchParams({ q })
      if (cat) params.set('cat', cat)
      const res = await fetch(`/api/search?${params}`)
      if (!res.ok) throw new Error(res.statusText)
      const data = await res.json()
      setResults(data)
    } catch (e: any) {
      setError(e.message)
    } finally {
      setLoading(false)
    }
  }, [])

  useEffect(() => {
    if (query.length < 2) return
    debounceRef.current = setTimeout(() => fetchResults(query, category), 300)
    return () => clearTimeout(debounceRef.current)
  }, [query, category, fetchResults])

  return (
    <div className="search-form">
      <input
        type="search"
        value={query}
        onChange={e => setQuery(e.target.value)}
        onKeyDown={e => e.key === 'Enter' && fetchResults(query, category)}
        placeholder={placeholder}
      />
      <select value={category} onChange={e => setCategory(e.target.value)}>
        <option value="">全部分类</option>
        {categories.map(cat => (
          <option key={cat} value={cat}>{cat}</option>
        ))}
      </select>
      <button
        disabled={loading}
        onClick={() => fetchResults(query, category)}
      >
        {loading ? '搜索中...' : '搜索'}
      </button>

      {error && <p className="error">{error}</p>}
      {loading && <div className="spinner" />}
      {!loading && results.length > 0 && (
        <ul>
          {results.map(item => (
            <li key={item.id}>
              <a href={item.url}>{item.title}</a>
              <span className="badge">{item.category}</span>
            </li>
          ))}
        </ul>
      )}
      {!loading && query && results.length === 0 && <p>没有找到结果</p>}
    </div>
  )
}

对比总结

方面Vue3React
条件渲染v-if / v-else / v-show 指令&& / ?: 表达式
列表渲染v-for + :key.map() + key prop
事件绑定@click / @submit 指令onClick / onSubmit props
双向绑定v-modelvalue + onChange
class绑定:class=”{ active: isActive }”className + 模板字符串
样式绑定:style=”styleObj”style={{ }}
DOM引用ref 属性useRef()

底层原理

1. 模板的编译优化机制

Vue模板的编译阶段做了大量React JSX难以实现的优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 编译前 -->
<template>
  <div class="wrapper">
    <p>静态文本</p>
    <span>{{ dynamic }}</span>
  </div>
</template>

<!-- 编译后的渲染函数(简化) -->
import { createElementVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from 'vue'

// 静态节点提升——只需要创建一次
const _hoisted_1 = _createVNode('p', null, '静态文本', -1 /* HOISTED */)

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock('div', { class: 'wrapper' }, [
    _hoisted_1,  // 直接复用静态节点
    _createVNode('span', null, _toDisplayString(_ctx.dynamic), 1 /* TEXT */)
  ]))
}

关键优化:

  1. 静态提升(Static Hoisting):不变的节点提升到渲染函数外部,只创建一次
  2. patchFlags:每个动态节点标记变化类型(TEXT、CLASS、STYLE、PROPS等)
  3. Block Tree:只追踪动态节点的变化树
  4. 事件缓存:内联事件处理函数自动缓存为稳定引用

这些优化在JSX中很难实现,因为JSX的灵活性使得编译器无法静态分析可提升的节点。

2. React.createElement的执行

JSX在React中被编译为React.createElement调用:

1
2
3
4
5
6
7
8
9
10
11
// JSX
<div className="wrapper">
  <p>静态文本</p>
  <span>{dynamic}</span>
</div>

// 编译后
React.createElement('div', { className: 'wrapper' },
  React.createElement('p', null, '静态文本'),
  React.createElement('span', null, dynamic),
)

React的createElement在每次渲染时都会执行,生成新的虚拟DOM对象。每次渲染都重新创建所有节点,然后通过Diff找出实际变化的部分。

React 19的React Compiler通过编译时分析来减少不必要的重新创建,但受限于JSX的动态性,其优化效果不如Vue模板的静态分析。

3. Options API的底层实现

选项式API的底层原理是选项合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function createComponent(Component) {
  // 提取各种选项
  const { data, methods, computed, watch, lifecycle } = Component

  return {
    setup() {
      // data → reactive对象
      const state = reactive(typeof data === 'function' ? data() : data)

      // methods → 绑定到实例
      const methodsMap = {}
      for (const key in methods) {
        methodsMap[key] = methods[key].bind(this)
      }

      // computed → 计算属性
      const computedMap = {}
      for (const key in computed) {
        computedMap[key] = computed(computed[key], this)
      }

      // watch → 监听器
      for (const key in watch) {
        watchEffect(() => {
          const newVal = state[key]
          // ...
        })
      }

      // 注入生命周期

      return { ...state, ...methodsMap, ...computedMap }
    }
  }
}

Composition API的引入并没有改变Vue的底层架构,它只是在setup()函数内部暴露了构建响应式系统的原子API,让开发者可以直接使用refreactivecomputedwatch等底层函数。

高频面试题解析

面试题1:Vue的模板语法和React的JSX语法哪个更好?

问题:Vue的模板语法和React的JSX语法各自的优劣势是什么?如何选择?

答案:不存在绝对的”更好”,两者有明确的不同适用场景和设计目标。

模板语法的优势

  1. 可读性强:HTML结构清晰,设计师和后端开发者也能看懂
  2. 编译优化:静态分析能力强,可以做Block Tree、静态提升等深度优化
  3. 学习曲线平缓:类似HTML,不需要JavaScript知识也能入门

模板语法的劣势

  1. 表达力受限:模板内无法使用高级JavaScript特性
  2. 动态逻辑需要额外封装:复杂逻辑需要在script中处理
  3. 类型支持较弱:TypeScript在模板中的类型推导不如JSX

JSX语法的优势

  1. JavaScript全能力:任何JavaScript表达式都可以用在JSX中
  2. 类型友好:TypeScript无缝支持
  3. 动态渲染更自然:map/reduce/filter等直接内联

JSX语法的劣势

  1. 编译器优化受限:JSX的动态性导致编译时优化不如模板
  2. 代码可读性不稳定:过度使用内联逻辑可能造成混乱
  3. 依赖数组管理:需要手动维护依赖数组(直到React Compiler普及)

选择建议:Vue适合模板驱动的、内容展示型的应用开发;React适合逻辑密集型的、需要高度动态交互的应用开发。

面试题2:为什么React Hooks要求在顶层调用,而Vue的setup没有这个限制?

问题:React Hooks的”只在顶层调用”规则背后的实现原因是什么?

答案:这个差异源于两种机制不同的底层实现:

React Hooks使用链表结构

  • 每个组件维护一个Hook链表(memoizedState
  • Hook的调用顺序对应链表节点在链表中的位置
  • 每次渲染时,React按顺序从链表头依次取出并执行Hook
  • 如果条件或循环导致调用顺序变化,链表读取就会错位
1
2
3
4
5
6
7
8
// 第一次渲染
const [a] = useState(1)  // hook → fiber.memoizedState[0]
const [b] = useState(2)  // hook → fiber.memoizedState[1]
// 链表: [hookA, hookB]

// 第二次渲染(假设条件导致hookA被跳过)
// ❌ useState(2)的调用会错误地读取hookA
// 链表顺序: React预期[0]是第一个useState,但实际是第二个

Vue Composition API基于响应式对象

  • ref()创建一个独立的响应式对象
  • computed()、”watch()`订阅的是响应式对象的变化
  • 这些操作不依赖”调用位置”,只依赖”操作了什么对象”
  • 因此可以在条件、循环中安全使用

简单来说:React Hooks依赖于”函数被调用的顺序列表”,Vue Composition API依赖于”响应式对象的引用”。

面试题3:Vue3中为什么推荐使用Composition API而不是Options API?

问题:Vue3官方推荐使用Composition API,但Options API仍然是可选的。两者的选择依据是什么?

答案:官方推荐是”按场景选择”,而非完全取代:

选择Options API的场景

  1. 小型组件(几十行代码以内)
  2. 简单展示型组件(没有复杂的交互逻辑)
  3. 团队中有较多的Vue2迁移背景的开发者
  4. 组件逻辑按”数据/方法/计算属性”自然划分

选择Composition API的场景

  1. 中大型组件(逻辑关注点多且交错)
  2. 需要逻辑复用的场景(抽取Composable)
  3. TypeScript重度用户(类型推导更优)
  4. 需要精细控制响应式依赖的场景
  5. 大型项目中的功能模块划分

Composition API带来的核心变化是:逻辑可以按”功能关注点”而非”选项类型”来组织。本质上,它让Vue拥有了像React Hooks一样的逻辑组合能力,但避开了React Hooks的调用顺序限制。

Vue团队的建议是:小项目用Options API,大项目用Composition API。同一个项目中也可以混用——Composition API被设计为向下兼容Options API的。

总结与扩展

Vue和React的API设计哲学差异,归根结底是”HTML优先”和”JavaScript优先”的差异:

哲学维度VueReact
出发点HTML增强JS驱动一切
模板语言声明式指令模板JSX(JS语法扩展)
组件定义Options API / Composition API函数组件 + Hooks
逻辑复用Mixins → ComposableHooks
状态管理响应式(Proxy)不可变(setState)
学习路径HTML → 指令 → 组件 → 生态JS → JSX → 组件 → Hooks → 生态

扩展思考

Solid.js和Qwik等新兴框架正在探索”更优的设计哲学”:Solid.js保留了JSX但采用了Vue式的细粒度响应式;Qwik采用了”可恢复性”(Resumability)的全新范式。

Vue的Vapor Mode尝试抛弃虚拟DOM,直接编译到细粒度DOM更新,这会进一步模糊模板和”响应式JSX”之间的界限。

从更宏观的视角看,前端框架的API设计哲学正在走向”殊途同归”:无论是Vue的Composition API向React Hooks靠拢,还是React Compiler向Vue的自动优化靠拢,两大框架都在吸收对方的精华。最终,工具的选择可能更多地取决于团队偏好和生态需求,而非根本性的技术差异。

本文由作者按照 CC BY 4.0 进行授权