Bootstrap

Vue 开发者的 React 实战指南:快速入门与思维转换

作为一名 Vue 开发者,第一次接触 React 时可能会感到困惑。虽然两者都是优秀的前端框架,但在开发思维和实现方式上存在较大差异。本文将通过实战示例,帮助你快速掌握 React 开发的核心概念和基本用法。

开发环境搭建

首先,我们需要搭建 React 开发环境。React 官方提供了脚手架工具 Create React App:

# 创建项目
npx create-react-app my-react-app

# 进入项目目录
cd my-react-app

# 启动开发服务器
npm start

如果你习惯使用 Vite,也可以使用 Vite 创建 React 项目:

# 使用 Vite 创建项目
npm create vite@latest my-react-app -- --template react

# 进入项目目录
cd my-react-app

# 安装依赖
npm install

# 启动开发服务器
npm run dev

基本语法对比

1. 组件定义

Vue 组件(SFC):

<!-- HelloWorld.vue -->
<template>
  <div class="hello">
    <h1>{{ message }}</h1>
    <button @click="handleClick">点击</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      message: 'Hello Vue!'
    }
  },
  methods: {
    handleClick() {
      this.message = 'Clicked!'
    }
  }
}
</script>

<style scoped>
.hello {
  color: blue;
}
</style>

React 组件:

// HelloWorld.jsx
import React, { useState } from 'react';
import './HelloWorld.css';

function HelloWorld() {
  const [message, setMessage] = useState('Hello React!');

  const handleClick = () => {
    setMessage('Clicked!');
  };

  return (
    <div className="hello">
      <h1>{message}</h1>
      <button onClick={handleClick}>点击</button>
    </div>
  );
}

export default HelloWorld;

主要区别:

  1. React 使用 JSX 而不是模板语法
  2. React 使用 useState 管理状态,而不是 data 选项
  3. React 的事件处理器直接定义为函数
  4. React 的样式需要单独引入

2. 数据绑定

Vue 的双向绑定:

<template>
  <input v-model="text" />
  <p>{{ text }}</p>
</template>

<script>
export default {
  data() {
    return {
      text: ''
    }
  }
}
</script>

React 的受控组件:

function InputComponent() {
  const [text, setText] = useState('');

  return (
    <>
      <input 
        value={text} 
        onChange={(e) => setText(e.target.value)} 
      />
      <p>{text}</p>
    </>
  );
}

主要区别:

  1. React 没有双向绑定语法糖
  2. React 需要手动处理 onChange 事件
  3. React 使用受控组件模式

3. 条件渲染

Vue 的条件渲染:

<template>
  <div>
    <h1 v-if="show">显示</h1>
    <h1 v-else>隐藏</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  }
}
</script>

React 的条件渲染:

function ConditionalComponent() {
  const [show, setShow] = useState(true);

  return (
    <div>
      {show ? <h1>显示</h1> : <h1>隐藏</h1>}
    </div>
  );
}

主要区别:

  1. React 使用 JavaScript 的条件运算符
  2. React 可以直接在 JSX 中使用表达式
  3. React 没有特殊的指令语法

4. 列表渲染

Vue 的列表渲染:

<template>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ item.text }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目 1' },
        { id: 2, text: '项目 2' },
        { id: 3, text: '项目 3' }
      ]
    }
  }
}
</script>

React 的列表渲染:

function ListComponent() {
  const [items, setItems] = useState([
    { id: 1, text: '项目 1' },
    { id: 2, text: '项目 2' },
    { id: 3, text: '项目 3' }
  ]);

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  );
}

主要区别:

  1. React 使用 map 方法而不是 v-for 指令
  2. React 的 key 属性直接写在元素上
  3. React 可以直接在 JSX 中使用数组方法

实战示例:Todo List

让我们通过一个简单的 Todo List 来实践上述概念。

Vue 版本:

<!-- TodoList.vue -->
<template>
  <div class="todo-list">
    <div class="todo-input">
      <input 
        v-model="newTodo" 
        @keyup.enter="addTodo"
        placeholder="输入待办事项"
      />
      <button @click="addTodo">添加</button>
    </div>

    <ul class="todo-items">
      <li 
        v-for="todo in todos" 
        :key="todo.id"
        :class="{ completed: todo.completed }"
      >
        <input
          type="checkbox"
          v-model="todo.completed"
        />
        <span>{{ todo.text }}</span>
        <button @click="removeTodo(todo.id)">删除</button>
      </li>
    </ul>

    <div class="todo-stats">
      剩余: {{ remaining }} 项
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      newTodo: '',
      todos: []
    }
  },
  computed: {
    remaining() {
      return this.todos.filter(todo => !todo.completed).length
    }
  },
  methods: {
    addTodo() {
      if (!this.newTodo.trim()) return

      this.todos.push({
        id: Date.now(),
        text: this.newTodo,
        completed: false
      })

      this.newTodo = ''
    },
    removeTodo(id) {
      this.todos = this.todos.filter(todo => todo.id !== id)
    }
  }
}
</script>

<style scoped>
.todo-list {
  max-width: 400px;
  margin: 20px auto;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.todo-input {
  display: flex;
  margin-bottom: 20px;
}

.todo-input input {
  flex: 1;
  padding: 8px;
  margin-right: 10px;
}

.todo-items li {
  display: flex;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid #eee;
}

.todo-items li.completed span {
  text-decoration: line-through;
  color: #999;
}

.todo-items li span {
  flex: 1;
  margin: 0 10px;
}

.todo-stats {
  margin-top: 20px;
  color: #666;
}
</style>

React 版本:

// TodoList.jsx
import React, { useState, useMemo } from 'react';
import './TodoList.css';

function TodoList() {
  const [newTodo, setNewTodo] = useState('');
  const [todos, setTodos] = useState([]);

  // 使用 useMemo 缓存计算结果
  const remaining = useMemo(() => {
    return todos.filter(todo => !todo.completed).length;
  }, [todos]);

  const addTodo = () => {
    if (!newTodo.trim()) return;

    setTodos([
      ...todos,
      {
        id: Date.now(),
        text: newTodo,
        completed: false
      }
    ]);

    setNewTodo('');
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo =>
      todo.id === id
        ? { ...todo, completed: !todo.completed }
        : todo
    ));
  };

  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <div className="todo-list">
      <div className="todo-input">
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          onKeyPress={(e) => e.key === 'Enter' && addTodo()}
          placeholder="输入待办事项"
        />
        <button onClick={addTodo}>添加</button>
      </div>

      <ul className="todo-items">
        {todos.map(todo => (
          <li
            key={todo.id}
            className={todo.completed ? 'completed' : ''}
          >
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.text}</span>
            <button onClick={() => removeTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>

      <div className="todo-stats">
        剩余: {remaining} 项
      </div>
    </div>
  );
}

export default TodoList;
/* TodoList.css */
.todo-list {
  max-width: 400px;
  margin: 20px auto;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.todo-input {
  display: flex;
  margin-bottom: 20px;
}

.todo-input input {
  flex: 1;
  padding: 8px;
  margin-right: 10px;
}

.todo-items li {
  display: flex;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid #eee;
}

.todo-items li.completed span {
  text-decoration: line-through;
  color: #999;
}

.todo-items li span {
  flex: 1;
  margin: 0 10px;
}

.todo-stats {
  margin-top: 20px;
  color: #666;
}

主要区别:

  1. 状态管理方式不同
    • Vue 使用 data 和 methods
    • React 使用 useState 和普通函数
  2. 计算属性实现不同
    • Vue 使用 computed
    • React 使用 useMemo
  3. 事件处理方式不同
    • Vue 使用 @ 或 v-on 指令
    • React 使用驼峰命名的事件属性
  4. 样式处理方式不同
    • Vue 使用 scoped style
    • React 使用单独的 CSS 文件

开发思维转换

  1. 声明式编程

    • Vue 和 React 都是声明式的
    • 但 React 更倾向使用 JavaScript 的方式解决问题
    • Vue 提供了更多的模板语法糖
  2. 数据不可变性

    • Vue 的响应式系统允许直接修改数据
    • React 推崇数据不可变性,总是创建新的状态
  3. 组件设计

    • Vue 的单文件组件更注重关注点分离
    • React 推崇 JSX,将模板和逻辑紧密结合
  4. 状态管理

    • Vue 的响应式系统使状态管理更简单
    • React 需要手动管理状态更新

注意事项

  1. 避免直接修改状态

    // 错误
    const [todos, setTodos] = useState([]);
    todos.push(newTodo);  // 直接修改状态
    
    // 正确
    setTodos([...todos, newTodo]);  // 创建新的状态
  2. 正确使用 useEffect

    // 错误
    useEffect(() => {
      // 没有依赖数组,每次渲染都会执行
    });
    
    // 正确
    useEffect(() => {
      // 只在依赖项变化时执行
    }, [dependency]);
  3. 合理使用 useMemo 和 useCallback

    // 不必要的使用
    const simple = useMemo(() => a + b, [a, b]);  // 简单计算不需要缓存
    
    // 合理使用
    const complex = useMemo(() => expensiveCalculation(a, b), [a, b]);
  4. 避免过度解构

    // 可能导致重复渲染
    const { value1, value2, value3 } = useContext(MyContext);
    
    // 更好的方式
    const context = useContext(MyContext);

调试技巧

  1. 使用 React Developer Tools

    • 安装 Chrome 扩展
    • 查看组件结构
    • 检查 props 和 state
    • 性能分析
  2. 使用 console.log 调试

    useEffect(() => {
      console.log('Component mounted');
      return () => console.log('Component will unmount');
    }, []);
  3. 使用 React.StrictMode

    import { StrictMode } from 'react';
    
    root.render(
      <StrictMode>
        <App />
      </StrictMode>
    );

小结

  1. React 和 Vue 的主要区别:

    • 模板语法 vs JSX
    • 响应式系统 vs 单向数据流
    • 指令系统 vs JavaScript 表达式
    • 生命周期 vs Hooks
  2. React 开发的关键点:

    • 理解 JSX
    • 掌握 Hooks
    • 遵循不可变性
    • 合理使用缓存
  3. 开发建议:

    • 多写 JavaScript,少依赖框架特性
    • 保持组件纯函数的特性
    • 合理拆分组件
    • 注意性能优化

下一篇文章,我们将深入探讨 React 的状态管理,帮助你更好地理解和使用 React 的状态管理方案。

如果觉得这篇文章对你有帮助,别忘了点个赞 👍

;