从原生 JS 到 React:手把手带你开启 React 业务开发之旅
一、前端开发演进:从切图崽到全干工程师的成长路径
在开始React业务开发之前,让我们先了解前端工程师的成长路线:
- 前端切图崽:掌握HTML+CSS+基础JS,能实现静态页面
- 前端开发工程师:掌握Vue/React等框架,能开发复杂Web应用
- 全栈开发工程师:掌握Node.js+数据库,实现前后端一体化
- 跨平台应用开发:掌握React Native等,开发Android/iOS应用
- AI应用开发:整合AI能力,开发智能化应用
- 全干工程师:掌握全技术栈,独立完成项目全流程
React作为现代前端开发的三大框架之一,让开发者得以脱离底层 DOM 操作,聚焦于业务逻辑,是我们进阶路上的重要里程碑。
二、React 项目初始化:从 vite 模板开始
1. 核心工具:npm 与 vite 的作用
-
npm(Node Package Manager) :作为 Node.js 的包管理器,负责安装 React 等开发依赖,如通过
npm install react react-dom
引入核心库。 - vite:新一代前端构建工具,相比传统 webpack,具有极速冷启动、按需编译的特点,尤其适合 React 项目的工程化搭建。
2. 快速创建项目的 4 步流程
# 1. 初始化vite项目(选择react模板语言选择js)
npm init vite@latest my-react-app -- --template react
# 2. 进入项目目录
cd my-react-app
# 3. 安装依赖
npm install
# 4. 启动开发服务器(默认端口5173)
npm run dev
执行完成后,浏览器访问http://localhost:5173
即可看到 React 欢迎页面
项目结构如下:
三、React 初体验:组件化开发的核心逻辑
1. 组件:HTML+CSS+JS 的封装单元
React 组件是完成开发任务的最小单元,它将 HTML、CSS、JS 逻辑封装在一个函数中,通过 组件组合 构建完整页面。以下是一个基础的 App
组件示例,演示如何通过函数组件渲染静态数据:
import { useState } from 'react';
import './App.css';
// 函数组件:接收参数并返回 JSX 结构
function App() {
const staticTodos = ['吃饭', '睡觉', '打豆豆'];
return (
<>
<table>
<thead>
<tr>
<th>序号</th>
<th>任务</th>
</tr>
</thead>
<tbody>
{/* 在 JSX 中通过 {} 嵌入 JS 表达式,使用 map 遍历渲染列表 */}
{staticTodos.map((item, index) => (
<tr key={index}> {/* 列表渲染必须指定唯一 key,此处仅为演示,实际应使用数据唯一标识 */}
<td>{index + 1}</td>
<td>{item}</td>
</tr>
))}
</tbody>
</table>
</>
);
}
export default App;
关键知识点:
-
JSX 语法:允许在 JS 中直接书写类 HTML 结构,
{}
用于插入 JS 表达式(如变量、函数调用等)。 -
列表渲染:通过
Array.map()
遍历数据并生成多个 JSX 元素,需为每个列表项指定唯一key
(React 用于追踪组件更新的标识)。 -
组件结构:函数组件必须有一个根返回值(如
<>
空标签或<div>
等容器元素)。
2. 响应式数据:useState Hook 实现数据驱动视图
当数据需要动态变更并触发视图更新时,需使用 React 的 状态(State) 机制。通过 useState
Hook 声明响应式数据,数据变更时 React 会自动重新渲染组件。
示例:动态更新标题与任务列表
import { useState } from 'react';
import './App.css';
function App() {
// 声明响应式状态:todos(任务列表)和 title(页面标题)
const [todos, setTodos] = useState(['吃饭', '睡觉', '打豆豆']);
const [title, setTitle] = useState('今天你吃饭了吗');
// 模拟异步操作:5 秒后更新
setTimeout(() => {
setTitle('今天你完成任务了吗');
setTodos([...todos, '养鱼']);
}, 5000);
return (
<div>
<h1 className="title">{title}</h1> {/* 绑定响应式数据 title */}
<table>
<thead>
<tr>
<th>序号</th>
<th>任务</th>
</tr>
</thead>
<tbody>
{todos.map((item, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{item}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default App;
CSS 样式(App.css):
* {
margin: 0;
padding: 0;
}
.title {
background-color: aqua;
color: aliceblue;
padding: 10px;
margin-bottom: 20px;
}
五秒后更新为:
核心概念解析:
-
useState 用法:
- 调用
useState(initialValue)
返回一个数组,第一项是状态值(如todos
),第二项是更新状态的函数(如setTodos
)。 - 状态更新函数(如
setTitle
)的调用会触发组件重新渲染,确保视图与数据同步。
- 调用
-
异步更新:即使在定时器、Promise 等异步场景中调用
setState
,React 也能正确捕获变更并更新视图。 -
数据不可变性:更新数组或对象状态时,需通过
[...todos]
或{...obj}
创建新对象,避免直接修改原数据导致 React 无法检测变更。
3. 组件组合:拆解复杂页面为可复用单元
React 推荐将页面拆分为多个小组件,通过嵌套组合实现复杂功能。以下是对示例代码的扩展思路 :
// 拆分标题组件
function Title({ title }) {
return <h1 className="title">{title}</h1>;
}
// 拆分任务列表组件
function TodoTable({ todos }) {
return (
<table>
<thead>
<tr>
<th>序号</th>
<th>任务</th>
</tr>
</thead>
<tbody>
{todos.map((item, index) => (
<tr key={index}>
<td>{index + 1}</td>
<td>{item}</td>
</tr>
))}
</tbody>
</table>
);
}
function App() {
const [todos, setTodos] = useState(['吃饭', '睡觉', '打豆豆']);
const [title, setTitle] = useState('今天你吃饭了吗');
setTimeout(() => setTitle('今天你完成任务了吗'), 5000);
return (
<div>
<Title title={title} /> {/* 父组件向子组件传递数据 via props */}
<TodoTable todos={todos} /> {/* 复用列表组件,传入不同数据 */}
</div>
);
}
组件通信规则:
-
父传子:通过 props 向子组件传递数据(如
Title
组件的title
属性)。 -
子传父:子组件通过回调函数(如
onClick={handleClick}
)向父组件传递事件。 - 跨组件通信:状态提升(将共享状态提升至共同父组件)或使用 Context 实现全局状态管理。
4. 对比原生 JS:React 如何简化开发
场景 | 原生 JS 实现方式 | React 实现方式 |
---|---|---|
渲染列表 | 使用 createElement 或 innerHTML 拼接 DOM 字符串 |
通过 JSX + map 直接声明视图结构 |
数据更新 | 手动查找 DOM 节点并更新 textContent /innerHTML
|
调用 setState 自动触发视图更新 |
组件复用 | 复制粘贴代码或封装函数返回 DOM 节点 | 定义函数组件并通过 props 传递差异化数据 |
核心优势:React 通过声明式编程(描述 “是什么” 而非 “怎么做”)和组件化架构,让开发者聚焦业务逻辑而非底层 DOM 操作,极大提升开发效率与代码可维护性。
四、实战案例:构建响应式待办事项应用
1. 功能拆解
我们将通过实际代码实现一个包含以下功能的待办应用:
- 输入框添加新任务(表单组件
TodoForm
) - 任务列表展示(列表组件
Todos
) - 数据持久化(暂存于组件状态,后续可扩展 localStorage)
2. 核心组件实现
首先我们先在src目录下创建一个components文件夹,存放各个组件
(1)表单组件TodoForm
:双向绑定与提交处理
import { useState } from 'react';
function TodoForm(props) {
// 从 props 中获取父组件传递的回调函数(用于接收新增任务)
const onAdd = props.onAdd;
// 声明本地状态:text 存储输入框内容,初始值为"打豆豆"
const [text, setText] = useState('打豆豆');
// 表单提交处理函数
const handleSubmit = (e) => {
// 阻止表单默认提交行为(避免页面跳转)
e.preventDefault();
// 调用父组件回调,传递当前输入的文本
onAdd(text);
console.log(e.target.value);
};
// 输入框内容变化处理函数
const handleChange = (e) => {
// 更新本地状态,实现双向绑定(输入框值与 state 同步)
setText(e.target.value);
};
return (
<form action="http://www.baidu.com" onSubmit={handleSubmit}>
<input
type="text"
placeholder="请输入待办事项"
value={text} // 绑定 state 到输入框 value
onChange={handleChange} // 监听输入变化并更新 state
/>
<button type="submit">添加</button>
</form>
);
}
export default TodoForm;
在这个组件中,通过useState
定义text
状态,实现输入框的双向绑定。handleChange
函数监听输入变化更新状态,handleSubmit
函数阻止表单默认提交,并调用父组件传递的onAdd
回调函数,将输入内容传递出去。
(2)列表组件Todos
:数据渲染与 key 的重要性
function Todos(props) {
console.log(props, '/////');
// 解构 props 中的 todos 数据
const todos = props.todos;
return (
<ul>
{
// 遍历 todos 数组,渲染为列表项
todos.map((todo) => (
// key 是 React 识别列表项的唯一标识,必须使用稳定的唯一值(如数据 ID)
<li key={todo.id}>{todo.text}</li>
))
}
</ul>
);
}
export default Todos;
该组件接收父组件传递的props.todos
数据,使用map
方法遍历数据并渲染为列表项。为每个列表项设置唯一的key
属性(这里使用todo.id
),这能帮助 React 高效地更新列表,避免在数据变化时出现渲染错误。
(3)核心逻辑组件TodoList
:状态管理与组件组合
import { useState } from 'react';
import '../Todo.css'; // 引入组件级样式(需确保路径正确)
import TodoForm from './TodoForm'; // 引入表单组件(子组件)
import Todos from './Todos'; // 引入列表组件(子组件)
function TodoList() {
const [hi, setHi] = useState('haha');
const [title, setTitle] = useState('Todo List');
const [todos, setTodos] = useState([
{
id: 1, // 唯一标识(必填,用于列表渲染和更新)
text: '吃饭',
completed: false
}
]);
// 添加任务的回调函数(供子组件调用)
const handleAdd = (text) => {
// 更新 todos 状态:使用展开运算符保留原有数据,新增任务项
setTodos([
...todos,
{
id: todos.length + 1,
text, // 解构参数作为任务文本
completed: false
}
]);
};
return (
<div className="container">
<h1 className="title">
{title}{hi} {/* 组合显示标题和演示文本 */}
</h1>
<TodoForm onAdd={handleAdd} /> {/* 向子组件传递回调函数,实现子传父通信 */}
<Todos todos={todos} /> {/* 向子组件传递数据,实现父传子通信 */}
</div>
);
}
export default TodoList;
TodoList
组件作为整个待办应用的核心,通过useState
定义了多个状态,如title
、todos
。handleAdd
函数用于处理添加任务的逻辑,当调用setTodos
更新任务列表状态时,React 会自动重新渲染包含Todos
组件的部分,实现数据驱动视图更新。同时,通过props
将handleAdd
函数传递给TodoForm
组件,完成子传父的通信。
(4)根组件App
:整合应用
import { useState } from 'react'
import './App.css'
import TodoList from './components/TodoList'
function App() {
return (
<>
<div>
</div>
{/* 渲染待办事项核心组件 */}
<TodoList />
</>
)
}
export default App
App
组件作为应用的入口,只负责引入并渲染TodoList
组件,将整个待办应用集成到项目中。
通过这个完整的待办事项应用案例,我们能更清晰地看到 React 中组件化开发、数据驱动视图、组件间通信等核心概念的实际应用。后续还可以在此基础上,添加任务删除、任务状态切换等功能,进一步深化对 React 开发的理解 。
学习 React 之路道阻且长,唯有持续深耕
从原生 JS 到 React,前端开发变得更加高效和便捷。React 的组件化开发和响应式数据管理,让开发者能够聚焦于业务逻辑,而无需过多关注底层的 DOM 操作。随着技术的不断发展,相信前端开发还会迎来更多的变革和创新。