React、Vue、Angular详解 | 从零基础到精通 | 包含状态管理和高级特性
Web框架是一套预先编写好的代码和工具,用来简化Web应用的开发。它提供了组件化、状态管理、路由等功能。
| 特性 | React | Vue | Angular |
|---|---|---|---|
| 学习难度 | 中等 | 简单 | 困难 |
| 性能 | 优秀 | 优秀 | 优秀 |
| 生态 | 最完善 | 完善 | 完善 |
| 社区 | 最活跃 | 活跃 | 活跃 |
| 适用场景 | 大型应用 | 中小型应用 | 企业应用 |
import React, { useState, useEffect } from 'react';
// 函数组件
function Counter() {
// useState Hook
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// useEffect Hook
useEffect(() => {
document.title = `Count: ${count}`;
// 清理函数
return () => {
document.title = 'React App';
};
}, [count]); // 依赖数组
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
/>
<p>Hello, {name}</p>
</div>
);
}
export default Counter;// useState - 状态
const [count, setCount] = useState(0);
// useEffect - 副作用
useEffect(() => {
// 组件挂载或依赖变化时执行
return () => {
// 清理函数
};
}, [dependencies]);
// useContext - 上下文
const theme = useContext(ThemeContext);
// useReducer - 复杂状态
const [state, dispatch] = useReducer(reducer, initialState);
// useCallback - 缓存函数
const memoizedCallback = useCallback(() => {
// 函数体
}, [dependencies]);
// useMemo - 缓存值
const memoizedValue = useMemo(() => {
return expensiveComputation(a, b);
}, [a, b]);
// useRef - 引用
const inputRef = useRef(null);// 父组件
function Parent() {
const [message, setMessage] = useState('');
return (
<div>
<Child
message={message}
onMessageChange={setMessage}
/>
</div>
);
}
// 子组件
function Child({ message, onMessageChange }) {
return (
<div>
<p>{message}</p>
<button onClick={() => onMessageChange('Hello')}>
Send Message
</button>
</div>
);
}function App() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const items = ['apple', 'banana', 'orange'];
return (
<div>
{/* 条件渲染 */}
{isLoggedIn ? (
<p>Welcome back!</p>
) : (
<p>Please log in</p>
)}
{/* 列表渲染 */}
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}<template>
<div>
<h1>Count: {{ count }}</h1>
<button @click="increment">Increment</button>
<input v-model="name" placeholder="Enter name">
<p>Hello, {{ name }}</p>
<ul>
<li v-for="item in items" :key="item">
{{ item }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
// 响应式数据
const count = ref(0);
const name = ref('');
const items = ref(['apple', 'banana', 'orange']);
// 方法
const increment = () => {
count.value++;
};
// 计算属性
const doubleCount = computed(() => count.value * 2);
// 监听器
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// 生命周期
onMounted(() => {
console.log('Component mounted');
});
</script>
<style scoped>
h1 {
color: blue;
}
</style>// 父组件
<template>
<Child
:message="message"
@update-message="message = $event"
/>
</template>
<script setup>
import { ref } from 'vue';
import Child from './Child.vue';
const message = ref('');
</script>
// 子组件 (Child.vue)
<template>
<div>
<p>{{ message }}</p>
<button @click="$emit('update-message', 'Hello')">
Send Message
</button>
</div>
</template>
<script setup>
defineProps({
message: String
});
defineEmits(['update-message']);
</script><template>
<div>
<!-- 条件渲染 -->
<p v-if="isLoggedIn">Welcome back!</p>
<p v-else>Please log in</p>
<!-- 列表渲染 -->
<ul>
<li v-for="item in items" :key="item">
{{ item }}
</li>
</ul>
<!-- 类和样式绑定 -->
<div :class="{ active: isActive }">
Dynamic class
</div>
<div :style="{ color: activeColor }">
Dynamic style
</div>
</div>
</template>import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<div>
<h1>Count: {{ count }}</h1>
<button (click)="increment()">Increment</button>
<input [(ngModel)]="name" placeholder="Enter name">
<p>Hello, {{ name }}</p>
<ul>
<li *ngFor="let item of items">
{{ item }}
</li>
</ul>
</div>
`,
styles: [`
h1 { color: blue; }
`]
})
export class CounterComponent {
count = 0;
name = '';
items = ['apple', 'banana', 'orange'];
@Input() message: string = '';
@Output() messageChange = new EventEmitter<string>();
increment() {
this.count++;
}
sendMessage() {
this.messageChange.emit('Hello');
}
}import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class UserService {
constructor(private http: HttpClient) {}
getUsers() {
return this.http.get('/api/users');
}
createUser(user: any) {
return this.http.post('/api/users', user);
}
}
// 在组件中使用
@Component({
selector: 'app-users',
template: `...`
})
export class UsersComponent {
users: any[] = [];
constructor(private userService: UserService) {}
ngOnInit() {
this.userService.getUsers().subscribe(
(data) => this.users = data
);
}
}import { createSlice, configureStore } from '@reduxjs/toolkit';
// 创建slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
}
}
});
// 创建store
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// 在组件中使用
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
const count = useSelector(state => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(counterSlice.actions.increment())}>
Increment
</button>
</div>
);
}import { createStore } from 'vuex';
const store = createStore({
state() {
return {
count: 0
};
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
},
getters: {
doubleCount: (state) => state.count * 2
}
});
// 在组件中使用
<template>
<div>
<p>Count: {{ $store.state.count }}</p>
<button @click="$store.commit('increment')">
Increment
</button>
</div>
</template>import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users/1">User 1</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
// 获取路由参数
import { useParams } from 'react-router-dom';
function UserDetail() {
const { id } = useParams();
return <div>User ID: {id}</div>;
}import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/users/:id', component: UserDetail },
{ path: '/:pathMatch(.*)*', component: NotFound }
];
const router = createRouter({
history: createWebHistory(),
routes
});
// 在模板中使用
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view />
</template>
// 获取路由参数
<script setup>
import { useRoute } from 'vue-router';
const route = useRoute();
const userId = route.params.id;
</script>import { useState, useEffect } from 'react';
function Component() {
const [count, setCount] = useState(0);
// 组件挂载和更新时执行
useEffect(() => {
console.log('Component mounted or updated');
// 清理函数(组件卸载时执行)
return () => {
console.log('Component unmounted');
};
}, []); // 空依赖数组 - 仅在挂载时执行
// 监听特定依赖
useEffect(() => {
console.log('Count changed:', count);
}, [count]);
return <div>Count: {count}</div>;
}<script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue';
// 挂载前
onBeforeMount(() => {
console.log('Before mount');
});
// 挂载后
onMounted(() => {
console.log('Mounted');
});
// 更新前
onBeforeUpdate(() => {
console.log('Before update');
});
// 更新后
onUpdated(() => {
console.log('Updated');
});
// 卸载前
onBeforeUnmount(() => {
console.log('Before unmount');
});
// 卸载后
onUnmounted(() => {
console.log('Unmounted');
});
</script>import { render, screen, fireEvent } from '@testing-library/react';
import Counter from './Counter';
describe('Counter Component', () => {
test('renders counter', () => {
render(<Counter />);
expect(screen.getByText(/Count: 0/i)).toBeInTheDocument();
});
test('increments count', () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
fireEvent.click(button);
expect(screen.getByText(/Count: 1/i)).toBeInTheDocument();
});
});import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter Component', () => {
it('renders counter', () => {
const wrapper = mount(Counter);
expect(wrapper.text()).toContain('Count: 0');
});
it('increments count', async () => {
const wrapper = mount(Counter);
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('Count: 1');
});
});src/
├── components/
│ ├── TaskList.jsx
│ ├── TaskForm.jsx
│ ├── TaskItem.jsx
│ └── Auth.jsx
├── pages/
│ ├── Home.jsx
│ ├── Login.jsx
│ └── NotFound.jsx
├── store/
│ ├── taskSlice.js
│ ├── authSlice.js
│ └── store.js
├── services/
│ └── api.js
├── App.jsx
└── main.jsx// store/taskSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchTasks = createAsyncThunk(
'tasks/fetchTasks',
async () => {
const response = await fetch('/api/tasks');
return response.json();
}
);
const taskSlice = createSlice({
name: 'tasks',
initialState: { items: [], loading: false },
extraReducers: (builder) => {
builder
.addCase(fetchTasks.pending, (state) => {
state.loading = true;
})
.addCase(fetchTasks.fulfilled, (state, action) => {
state.items = action.payload;
state.loading = false;
});
}
});
// components/TaskList.jsx
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchTasks } from '../store/taskSlice';
function TaskList() {
const dispatch = useDispatch();
const { items, loading } = useSelector(state => state.tasks);
useEffect(() => {
dispatch(fetchTasks());
}, [dispatch]);
if (loading) return <div>Loading...</div>;
return (
<ul>
{items.map(task => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}通过这个项目,你学会了如何使用现代Web框架构建完整的应用,包括状态管理、路由、API集成等。
现在你已经掌握了现代Web框架的核心知识。
选择一个框架深入学习,而不是浅尝所有框架。实践是最好的老师。