|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
现代Web开发React框架JavaScript编程教程详解
引言
React是由Facebook开发并开源的前端JavaScript库,用于构建用户界面,特别是单页应用程序。自2013年发布以来,React已经成为最受欢迎的前端框架之一,被无数大型网站和应用所采用。React的组件化架构、虚拟DOM和单向数据流等特性,使得开发复杂交互式Web应用变得更加高效和可维护。
本教程将全面介绍React框架的核心概念、实际应用和最佳实践,帮助读者从零开始掌握React开发,并能够构建现代化的Web应用程序。
React基础
JSX是JavaScript XML的缩写,它允许我们在JavaScript代码中编写类似HTML的语法。JSX使得编写React组件变得更加直观和简洁。
- // 基本的JSX语法
- const element = <h1>Hello, World!</h1>;
- // 使用表达式
- const name = 'John';
- const element = <h1>Hello, {name}</h1>;
- // JSX属性
- const element = <img src="avatar.jpg" alt="User Avatar" />;
- // 嵌套元素
- const element = (
- <div>
- <h1>Welcome</h1>
- <p>This is a paragraph.</p>
- </div>
- );
复制代码
需要注意的是,JSX并不是HTML,它在编译时会被转换为普通的JavaScript函数调用。React使用Babel来将JSX代码转换为React.createElement()调用。
React应用由组件构成,组件是可复用的、独立的UI单元。React中有两种类型的组件:函数组件和类组件。
函数组件是简单的JavaScript函数,接收props(属性)作为参数,并返回React元素。
- function Welcome(props) {
- return <h1>Hello, {props.name}</h1>;
- }
- // 或者使用箭头函数
- const Welcome = (props) => {
- return <h1>Hello, {props.name}</h1>;
- };
- // 更简洁的箭头函数形式
- const Welcome = (props) => <h1>Hello, {props.name}</h1>;
复制代码
类组件是继承自React.Component的ES6类,它们必须包含一个render()方法,用于返回React元素。
- class Welcome extends React.Component {
- render() {
- return <h1>Hello, {this.props.name}</h1>;
- }
- }
复制代码
在现代React开发中,函数组件配合Hooks(将在后面介绍)成为主流,但了解类组件仍然很重要,特别是在维护旧代码时。
Props(属性)是父组件传递给子组件的数据。Props是只读的,子组件不能直接修改它们。
- function Welcome(props) {
- return <h1>Hello, {props.name}</h1>;
- }
- function App() {
- return <Welcome name="John" />;
- }
复制代码
State是组件内部维护的数据,当state发生变化时,组件会重新渲染。
在函数组件中,我们使用useStateHook来管理state:
- import React, { useState } from 'react';
- function Counter() {
- // 声明一个新的state变量,初始值为0
- const [count, setCount] = useState(0);
- return (
- <div>
- <p>You clicked {count} times</p>
- <button onClick={() => setCount(count + 1)}>
- Click me
- </button>
- </div>
- );
- }
复制代码
在类组件中,state通过构造函数初始化,并通过this.setState()方法更新:
- class Counter extends React.Component {
- constructor(props) {
- super(props);
- this.state = { count: 0 };
- }
- render() {
- return (
- <div>
- <p>You clicked {this.state.count} times</p>
- <button onClick={() => this.setState({ count: this.state.count + 1 })}>
- Click me
- </button>
- </div>
- );
- }
- }
复制代码
React核心概念
类组件有特定的生命周期方法,这些方法在组件的不同阶段被调用:
1. 挂载阶段:组件被创建并插入到DOM中constructor(): 组件构造函数static getDerivedStateFromProps(): 在渲染前调用,用于根据props更新staterender(): 渲染组件componentDidMount(): 组件挂载后调用,适合进行API调用等副作用操作
2. constructor(): 组件构造函数
3. static getDerivedStateFromProps(): 在渲染前调用,用于根据props更新state
4. render(): 渲染组件
5. componentDidMount(): 组件挂载后调用,适合进行API调用等副作用操作
6. 更新阶段:组件的props或state发生变化static getDerivedStateFromProps(): 在渲染前调用,用于根据props更新stateshouldComponentUpdate(): 决定组件是否应该重新渲染render(): 渲染组件getSnapshotBeforeUpdate(): 在DOM更新前调用,可以获取DOM更新前的信息componentDidUpdate(): 组件更新后调用
7. static getDerivedStateFromProps(): 在渲染前调用,用于根据props更新state
8. shouldComponentUpdate(): 决定组件是否应该重新渲染
9. render(): 渲染组件
10. getSnapshotBeforeUpdate(): 在DOM更新前调用,可以获取DOM更新前的信息
11. componentDidUpdate(): 组件更新后调用
12. 卸载阶段:组件从DOM中移除componentWillUnmount(): 组件卸载前调用,适合进行清理工作
13. componentWillUnmount(): 组件卸载前调用,适合进行清理工作
挂载阶段:组件被创建并插入到DOM中
• constructor(): 组件构造函数
• static getDerivedStateFromProps(): 在渲染前调用,用于根据props更新state
• render(): 渲染组件
• componentDidMount(): 组件挂载后调用,适合进行API调用等副作用操作
更新阶段:组件的props或state发生变化
• static getDerivedStateFromProps(): 在渲染前调用,用于根据props更新state
• shouldComponentUpdate(): 决定组件是否应该重新渲染
• render(): 渲染组件
• getSnapshotBeforeUpdate(): 在DOM更新前调用,可以获取DOM更新前的信息
• componentDidUpdate(): 组件更新后调用
卸载阶段:组件从DOM中移除
• componentWillUnmount(): 组件卸载前调用,适合进行清理工作
- class LifecycleDemo extends React.Component {
- constructor(props) {
- super(props);
- this.state = { count: 0 };
- console.log('Constructor called');
- }
- componentDidMount() {
- console.log('Component mounted');
- // 可以在这里进行API调用
- document.title = `You clicked ${this.state.count} times`;
- }
- componentDidUpdate(prevProps, prevState) {
- console.log('Component updated');
- // 可以在这里根据state变化进行操作
- if (prevState.count !== this.state.count) {
- document.title = `You clicked ${this.state.count} times`;
- }
- }
- componentWillUnmount() {
- console.log('Component will unmount');
- // 清理工作,如清除定时器
- }
- render() {
- console.log('Component rendered');
- return (
- <div>
- <p>You clicked {this.state.count} times</p>
- <button onClick={() => this.setState({ count: this.state.count + 1 })}>
- Click me
- </button>
- </div>
- );
- }
- }
复制代码
Hooks是React 16.8引入的新特性,它允许你在不编写类组件的情况下使用state和其他React特性。Hooks只能在函数组件的顶层调用,不能在条件语句、循环或嵌套函数中调用。
useState用于在函数组件中添加state。
- import React, { useState } from 'react';
- function Counter() {
- const [count, setCount] = useState(0);
- // 可以有多个state变量
- const [name, setName] = useState('John');
- return (
- <div>
- <p>You clicked {count} times</p>
- <button onClick={() => setCount(count + 1)}>
- Click me
- </button>
- <p>Hello, {name}</p>
- <button onClick={() => setName('Jane')}>
- Change name
- </button>
- </div>
- );
- }
复制代码
useEffect用于处理副作用,如API调用、订阅、手动操作DOM等。它相当于类组件中的componentDidMount、componentDidUpdate和componentWillUnmount的组合。
- import React, { useState, useEffect } from 'react';
- function Example() {
- const [count, setCount] = useState(0);
- // 类似于componentDidMount和componentDidUpdate
- useEffect(() => {
- // 更新文档标题
- document.title = `You clicked ${count} times`;
- });
- // 只在挂载时执行一次,类似于componentDidMount
- useEffect(() => {
- console.log('Component mounted');
- // 可以在这里进行API调用
- fetch('https://api.example.com/data')
- .then(response => response.json())
- .then(data => console.log(data));
- }, []); // 空依赖数组表示只在挂载时执行
- // 在count变化时执行,类似于componentDidUpdate
- useEffect(() => {
- console.log('Count changed:', count);
- }, [count]); // 依赖数组包含count,表示count变化时执行
- // 清理副作用,类似于componentWillUnmount
- useEffect(() => {
- const timer = setInterval(() => {
- console.log('Timer tick');
- }, 1000);
- // 返回的函数会在组件卸载时执行
- return () => {
- clearInterval(timer);
- console.log('Timer cleared');
- };
- }, []);
- return (
- <div>
- <p>You clicked {count} times</p>
- <button onClick={() => setCount(count + 1)}>
- Click me
- </button>
- </div>
- );
- }
复制代码
useContext用于在组件间共享数据,避免props层层传递。
- import React, { createContext, useContext, useState } from 'react';
- // 创建Context
- const ThemeContext = createContext();
- function App() {
- const [theme, setTheme] = useState('light');
- return (
- // 使用Provider提供值
- <ThemeContext.Provider value={{ theme, setTheme }}>
- <Toolbar />
- </ThemeContext.Provider>
- );
- }
- function Toolbar() {
- return (
- <div>
- <ThemedButton />
- </div>
- );
- }
- function ThemedButton() {
- // 使用Consumer获取值
- const { theme, setTheme } = useContext(ThemeContext);
- return (
- <button
- style={{ background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#333' : '#fff' }}
- onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
- >
- Toggle Theme
- </button>
- );
- }
复制代码
useReducer是useState的替代方案,适用于复杂的state逻辑。
- import React, { useReducer } from 'react';
- // 定义reducer函数
- function reducer(state, action) {
- switch (action.type) {
- case 'increment':
- return { count: state.count + 1 };
- case 'decrement':
- return { count: state.count - 1 };
- default:
- throw new Error();
- }
- }
- function Counter() {
- // 使用useReducer
- const [state, dispatch] = useReducer(reducer, { count: 0 });
- return (
- <>
- Count: {state.count}
- <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
- <button onClick={() => dispatch({ type: 'increment' })}>+</button>
- </>
- );
- }
复制代码
自定义Hooks是一种复用状态逻辑的机制,它允许你将组件逻辑提取到可重用的函数中。
- import { useState, useEffect } from 'react';
- // 自定义Hook:处理窗口大小变化
- function useWindowWidth() {
- const [width, setWidth] = useState(window.innerWidth);
- useEffect(() => {
- const handleResize = () => setWidth(window.innerWidth);
- window.addEventListener('resize', handleResize);
-
- return () => {
- window.removeEventListener('resize', handleResize);
- };
- }, []);
- return width;
- }
- // 使用自定义Hook
- function ResponsiveComponent() {
- const width = useWindowWidth();
- return (
- <div>
- Current window width: {width}px
- {width < 768 ? <MobileView /> : <DesktopView />}
- </div>
- );
- }
- function MobileView() {
- return <div>This is mobile view</div>;
- }
- function DesktopView() {
- return <div>This is desktop view</div>;
- }
复制代码
Context提供了一种在组件树中共享数据的方式,而不必通过props逐层传递。当多个组件需要访问相同的数据时,Context非常有用。
- import React, { createContext, useContext, useState } from 'react';
- // 创建Context
- const UserContext = createContext();
- function App() {
- const [user, setUser] = useState({ name: 'John', age: 30 });
- return (
- // 使用Provider提供值
- <UserContext.Provider value={{ user, setUser }}>
- <Profile />
- <UpdateUser />
- </UserContext.Provider>
- );
- }
- function Profile() {
- // 使用Consumer获取值
- const { user } = useContext(UserContext);
- return (
- <div>
- <h2>Profile</h2>
- <p>Name: {user.name}</p>
- <p>Age: {user.age}</p>
- </div>
- );
- }
- function UpdateUser() {
- const { user, setUser } = useContext(UserContext);
- const updateName = () => {
- setUser({ ...user, name: 'Jane' });
- };
- return (
- <div>
- <h2>Update User</h2>
- <button onClick={updateName}>Change Name to Jane</button>
- </div>
- );
- }
复制代码
路由与导航
在单页应用中,路由是实现不同页面间导航的关键。React Router是React中最流行的路由库。
- npm install react-router-dom
复制代码- import React from 'react';
- import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
- function App() {
- return (
- <Router>
- <div>
- <nav>
- <ul>
- <li><Link to="/">Home</Link></li>
- <li><Link to="/about">About</Link></li>
- <li><Link to="/users">Users</Link></li>
- </ul>
- </nav>
- <Routes>
- <Route path="/" element={<Home />} />
- <Route path="/about" element={<About />} />
- <Route path="/users" element={<Users />} />
- </Routes>
- </div>
- </Router>
- );
- }
- function Home() {
- return <h2>Home</h2>;
- }
- function About() {
- return <h2>About</h2>;
- }
- function Users() {
- return <h2>Users</h2>;
- }
复制代码- import React from 'react';
- import { BrowserRouter as Router, Routes, Route, Link, useParams } from 'react-router-dom';
- function App() {
- return (
- <Router>
- <div>
- <nav>
- <ul>
- <li><Link to="/">Home</Link></li>
- <li><Link to="/users">Users</Link></li>
- </ul>
- </nav>
- <Routes>
- <Route path="/" element={<Home />} />
- <Route path="/users" element={<Users />} />
- <Route path="/users/:id" element={<UserDetail />} />
- </Routes>
- </div>
- </Router>
- );
- }
- function Home() {
- return <h2>Home</h2>;
- }
- function Users() {
- const users = [
- { id: 1, name: 'John' },
- { id: 2, name: 'Jane' },
- { id: 3, name: 'Bob' }
- ];
- return (
- <div>
- <h2>Users</h2>
- <ul>
- {users.map(user => (
- <li key={user.id}>
- <Link to={`/users/${user.id}`}>{user.name}</Link>
- </li>
- ))}
- </ul>
- </div>
- );
- }
- function UserDetail() {
- // 获取URL参数
- const { id } = useParams();
-
- // 模拟根据ID获取用户数据
- const users = {
- 1: { name: 'John', email: 'john@example.com' },
- 2: { name: 'Jane', email: 'jane@example.com' },
- 3: { name: 'Bob', email: 'bob@example.com' }
- };
-
- const user = users[id];
- if (!user) {
- return <div>User not found</div>;
- }
- return (
- <div>
- <h2>User Detail</h2>
- <p>Name: {user.name}</p>
- <p>Email: {user.email}</p>
- <Link to="/users">Back to Users</Link>
- </div>
- );
- }
复制代码- import React from 'react';
- import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
- function App() {
- return (
- <Router>
- <div>
- <nav>
- <ul>
- <li><Link to="/">Home</Link></li>
- <li><Link to="/products">Products</Link></li>
- </ul>
- </nav>
- <Routes>
- <Route path="/" element={<Home />} />
- <Route path="products" element={<Products />}>
- <Route path="featured" element={<FeaturedProducts />} />
- <Route path="new" element={<NewProducts />} />
- </Route>
- </Routes>
- </div>
- </Router>
- );
- }
- function Home() {
- return <h2>Home</h2>;
- }
- function Products() {
- return (
- <div>
- <h2>Products</h2>
- <nav>
- <ul>
- <li><Link to="/products/featured">Featured Products</Link></li>
- <li><Link to="/products/new">New Products</Link></li>
- </ul>
- </nav>
- {/* 嵌套路由的渲染位置 */}
- <Outlet />
- </div>
- );
- }
- function FeaturedProducts() {
- return <h3>Featured Products</h3>;
- }
- function NewProducts() {
- return <h3>New Products</h3>;
- }
复制代码- import React from 'react';
- import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom';
- function App() {
- return (
- <Router>
- <Routes>
- <Route path="/" element={<Home />} />
- <Route path="/about" element={<About />} />
- <Route path="/contact" element={<Contact />} />
- </Routes>
- </Router>
- );
- }
- function Home() {
- const navigate = useNavigate();
- const goToAbout = () => {
- navigate('/about');
- };
- return (
- <div>
- <h2>Home</h2>
- <button onClick={goToAbout}>Go to About</button>
- </div>
- );
- }
- function About() {
- const navigate = useNavigate();
- const goToContact = () => {
- navigate('/contact');
- };
- const goBack = () => {
- navigate(-1); // 返回上一页
- };
- return (
- <div>
- <h2>About</h2>
- <button onClick={goToContact}>Go to Contact</button>
- <button onClick={goBack}>Go Back</button>
- </div>
- );
- }
- function Contact() {
- return <h2>Contact</h2>;
- }
复制代码
状态管理
随着应用规模的扩大,组件间的状态共享和管理变得越来越复杂。虽然React的Context API可以解决一些问题,但对于大型应用,使用专门的状态管理库会更加高效。
Redux是一个可预测的状态容器,用于JavaScript应用。它可以帮助你管理应用的全局状态。
- npm install redux react-redux
复制代码- import React from 'react';
- import { createStore } from 'redux';
- import { Provider, useSelector, useDispatch } from 'react-redux';
- // 1. 定义action类型
- const INCREMENT = 'INCREMENT';
- const DECREMENT = 'DECREMENT';
- // 2. 定义action creators
- const increment = () => ({ type: INCREMENT });
- const decrement = () => ({ type: DECREMENT });
- // 3. 定义reducer
- function counterReducer(state = { count: 0 }, action) {
- switch (action.type) {
- case INCREMENT:
- return { count: state.count + 1 };
- case DECREMENT:
- return { count: state.count - 1 };
- default:
- return state;
- }
- }
- // 4. 创建store
- const store = createStore(counterReducer);
- function App() {
- return (
- // 5. 使用Provider提供store
- <Provider store={store}>
- <Counter />
- </Provider>
- );
- }
- function Counter() {
- // 6. 使用useSelector获取state
- const count = useSelector(state => state.count);
-
- // 7. 使用useDispatch获取dispatch函数
- const dispatch = useDispatch();
- return (
- <div>
- <h1>Counter: {count}</h1>
- <button onClick={() => dispatch(increment())}>Increment</button>
- <button onClick={() => dispatch(decrement())}>Decrement</button>
- </div>
- );
- }
复制代码
Redux Toolkit是Redux官方推荐的工具集,它简化了Redux的设置和使用。
- npm install @reduxjs/toolkit react-redux
复制代码- import React from 'react';
- import { configureStore, createSlice } from '@reduxjs/toolkit';
- import { Provider, useSelector, useDispatch } from 'react-redux';
- // 1. 创建slice
- const counterSlice = createSlice({
- name: 'counter',
- initialState: { count: 0 },
- reducers: {
- increment: (state) => {
- state.count += 1;
- },
- decrement: (state) => {
- state.count -= 1;
- },
- incrementByAmount: (state, action) => {
- state.count += action.payload;
- }
- }
- });
- // 导出action creators
- export const { increment, decrement, incrementByAmount } = counterSlice.actions;
- // 2. 配置store
- const store = configureStore({
- reducer: {
- counter: counterSlice.reducer
- }
- });
- function App() {
- return (
- <Provider store={store}>
- <Counter />
- </Provider>
- );
- }
- function Counter() {
- const count = useSelector((state) => state.counter.count);
- const dispatch = useDispatch();
- return (
- <div>
- <h1>Counter: {count}</h1>
- <button onClick={() => dispatch(increment())}>Increment</button>
- <button onClick={() => dispatch(decrement())}>Decrement</button>
- <button onClick={() => dispatch(incrementByAmount(5))}>Increment by 5</button>
- </div>
- );
- }
复制代码
Redux Thunk是一个中间件,允许你在Redux应用中编写异步逻辑。
- import React, { useEffect } from 'react';
- import { configureStore, createSlice } from '@reduxjs/toolkit';
- import { Provider, useSelector, useDispatch } from 'react-redux';
- import thunk from 'redux-thunk';
- // 用户slice
- const userSlice = createSlice({
- name: 'user',
- initialState: { user: null, loading: false, error: null },
- reducers: {
- fetchUserStart: (state) => {
- state.loading = true;
- state.error = null;
- },
- fetchUserSuccess: (state, action) => {
- state.loading = false;
- state.user = action.payload;
- },
- fetchUserFailure: (state, action) => {
- state.loading = false;
- state.error = action.payload;
- }
- }
- });
- export const { fetchUserStart, fetchUserSuccess, fetchUserFailure } = userSlice.actions;
- // 异步action creator
- export const fetchUser = (id) => {
- return async (dispatch) => {
- dispatch(fetchUserStart());
-
- try {
- const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
- const data = await response.json();
- dispatch(fetchUserSuccess(data));
- } catch (error) {
- dispatch(fetchUserFailure(error.message));
- }
- };
- };
- // 配置store
- const store = configureStore({
- reducer: {
- user: userSlice.reducer
- },
- middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(thunk)
- });
- function App() {
- return (
- <Provider store={store}>
- <UserProfile />
- </Provider>
- );
- }
- function UserProfile() {
- const { user, loading, error } = useSelector((state) => state.user);
- const dispatch = useDispatch();
- useEffect(() => {
- dispatch(fetchUser(1));
- }, [dispatch]);
- if (loading) return <div>Loading...</div>;
- if (error) return <div>Error: {error}</div>;
- if (!user) return null;
- return (
- <div>
- <h1>User Profile</h1>
- <p>Name: {user.name}</p>
- <p>Email: {user.email}</p>
- <p>Website: {user.website}</p>
- </div>
- );
- }
复制代码
MobX是另一个流行的状态管理库,它采用响应式编程模型,相比Redux更加简洁和直观。
- npm install mobx mobx-react-lite
复制代码- import React from 'react';
- import { makeAutoObservable } from 'mobx';
- import { observer } from 'mobx-react-lite';
- // 1. 创建store
- class CounterStore {
- count = 0;
- constructor() {
- makeAutoObservable(this);
- }
- increment() {
- this.count += 1;
- }
- decrement() {
- this.count -= 1;
- }
- }
- // 2. 实例化store
- const counterStore = new CounterStore();
- // 3. 创建组件并使用observer包装
- const Counter = observer(() => {
- return (
- <div>
- <h1>Counter: {counterStore.count}</h1>
- <button onClick={() => counterStore.increment()}>Increment</button>
- <button onClick={() => counterStore.decrement()}>Decrement</button>
- </div>
- );
- });
- function App() {
- return <Counter />;
- }
复制代码
与后端交互
现代Web应用通常需要与后端API进行交互,获取和提交数据。React中有多种方式可以实现这一点。
Fetch API是现代浏览器内置的用于网络请求的接口。
- import React, { useState, useEffect } from 'react';
- function UserList() {
- const [users, setUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- useEffect(() => {
- // 使用fetch获取数据
- fetch('https://jsonplaceholder.typicode.com/users')
- .then(response => {
- if (!response.ok) {
- throw new Error('Network response was not ok');
- }
- return response.json();
- })
- .then(data => {
- setUsers(data);
- setLoading(false);
- })
- .catch(error => {
- setError(error.message);
- setLoading(false);
- });
- }, []);
- if (loading) return <div>Loading...</div>;
- if (error) return <div>Error: {error}</div>;
- return (
- <div>
- <h1>User List</h1>
- <ul>
- {users.map(user => (
- <li key={user.id}>
- {user.name} - {user.email}
- </li>
- ))}
- </ul>
- </div>
- );
- }
复制代码
Axios是一个流行的HTTP客户端,它提供了比Fetch API更丰富的功能。
- import React, { useState, useEffect } from 'react';
- import axios from 'axios';
- function UserList() {
- const [users, setUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- useEffect(() => {
- // 使用axios获取数据
- axios.get('https://jsonplaceholder.typicode.com/users')
- .then(response => {
- setUsers(response.data);
- setLoading(false);
- })
- .catch(error => {
- setError(error.message);
- setLoading(false);
- });
- }, []);
- if (loading) return <div>Loading...</div>;
- if (error) return <div>Error: {error}</div>;
- return (
- <div>
- <h1>User List</h1>
- <ul>
- {users.map(user => (
- <li key={user.id}>
- {user.name} - {user.email}
- </li>
- ))}
- </ul>
- </div>
- );
- }
复制代码- import React, { useState } from 'react';
- import axios from 'axios';
- function CreateUser() {
- const [name, setName] = useState('');
- const [email, setEmail] = useState('');
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const [success, setSuccess] = useState(false);
- const handleSubmit = (e) => {
- e.preventDefault();
- setLoading(true);
- setError(null);
- setSuccess(false);
- const newUser = {
- name,
- email,
- username: name.toLowerCase().replace(/\s+/g, '')
- };
- axios.post('https://jsonplaceholder.typicode.com/users', newUser)
- .then(response => {
- console.log('User created:', response.data);
- setSuccess(true);
- setName('');
- setEmail('');
- setLoading(false);
- })
- .catch(error => {
- setError(error.message);
- setLoading(false);
- });
- };
- return (
- <div>
- <h1>Create User</h1>
- {success && <div style={{ color: 'green' }}>User created successfully!</div>}
- {error && <div style={{ color: 'red' }}>Error: {error}</div>}
- <form onSubmit={handleSubmit}>
- <div>
- <label>Name:</label>
- <input
- type="text"
- value={name}
- onChange={(e) => setName(e.target.value)}
- required
- />
- </div>
- <div>
- <label>Email:</label>
- <input
- type="email"
- value={email}
- onChange={(e) => setEmail(e.target.value)}
- required
- />
- </div>
- <button type="submit" disabled={loading}>
- {loading ? 'Creating...' : 'Create User'}
- </button>
- </form>
- </div>
- );
- }
复制代码
React Query是一个强大的数据获取库,它提供了缓存、后台更新、乐观更新等功能。
- import React from 'react';
- import { QueryClient, QueryClientProvider, useQuery } from 'react-query';
- import axios from 'axios';
- // 创建QueryClient
- const queryClient = new QueryClient();
- // 获取用户数据的函数
- const fetchUsers = async () => {
- const { data } = await axios.get('https://jsonplaceholder.typicode.com/users');
- return data;
- };
- function UserList() {
- // 使用useQuery获取数据
- const { data: users, isLoading, error } = useQuery('users', fetchUsers);
- if (isLoading) return <div>Loading...</div>;
- if (error) return <div>Error: {error.message}</div>;
- return (
- <div>
- <h1>User List</h1>
- <ul>
- {users.map(user => (
- <li key={user.id}>
- {user.name} - {user.email}
- </li>
- ))}
- </ul>
- </div>
- );
- }
- function App() {
- return (
- // 使用QueryClientProvider提供QueryClient
- <QueryClientProvider client={queryClient}>
- <UserList />
- </QueryClientProvider>
- );
- }
复制代码
样式处理
在React应用中,有多种方式可以处理组件样式。每种方法都有其优缺点,适用于不同的场景。
内联样式是直接在JSX元素上使用style属性定义样式。
- function Button() {
- const buttonStyle = {
- backgroundColor: '#4CAF50',
- border: 'none',
- color: 'white',
- padding: '15px 32px',
- textAlign: 'center',
- textDecoration: 'none',
- display: 'inline-block',
- fontSize: '16px',
- margin: '4px 2px',
- cursor: 'pointer',
- borderRadius: '4px'
- };
- return <button style={buttonStyle}>Click Me</button>;
- }
复制代码
CSS Modules允许你将CSS文件限定在特定组件范围内,避免全局样式冲突。
- /* Button.module.css */
- .button {
- background-color: #4CAF50;
- border: none;
- color: white;
- padding: 15px 32px;
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 16px;
- margin: 4px 2px;
- cursor: pointer;
- border-radius: 4px;
- }
- .button:hover {
- background-color: #45a049;
- }
复制代码- import React from 'react';
- import styles from './Button.module.css';
- function Button() {
- return <button className={styles.button}>Click Me</button>;
- }
复制代码
Styled Components是一个流行的CSS-in-JS库,它允许你在JavaScript中编写CSS。
- npm install styled-components
复制代码- import React from 'react';
- import styled from 'styled-components';
- // 创建styled组件
- const StyledButton = styled.button`
- background-color: ${props => props.primary ? '#4CAF50' : 'white'};
- color: ${props => props.primary ? 'white' : '#4CAF50'};
- border: 2px solid #4CAF50;
- padding: 15px 32px;
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 16px;
- margin: 4px 2px;
- cursor: pointer;
- border-radius: 4px;
-
- &:hover {
- background-color: ${props => props.primary ? '#45a049' : '#f1f1f1'};
- }
- `;
- function Button() {
- return (
- <div>
- <StyledButton>Normal Button</StyledButton>
- <StyledButton primary>Primary Button</StyledButton>
- </div>
- );
- }
复制代码
Emotion是另一个流行的CSS-in-JS库,它提供了强大的样式解决方案。
- npm install @emotion/react @emotion/styled
复制代码- import React from 'react';
- import styled from '@emotion/styled';
- // 创建styled组件
- const StyledButton = styled.button`
- background-color: ${props => props.primary ? '#4CAF50' : 'white'};
- color: ${props => props.primary ? 'white' : '#4CAF50'};
- border: 2px solid #4CAF50;
- padding: 15px 32px;
- text-align: center;
- text-decoration: none;
- display: inline-block;
- font-size: 16px;
- margin: 4px 2px;
- cursor: pointer;
- border-radius: 4px;
-
- &:hover {
- background-color: ${props => props.primary ? '#45a049' : '#f1f1f1'};
- }
- `;
- function Button() {
- return (
- <div>
- <StyledButton>Normal Button</StyledButton>
- <StyledButton primary>Primary Button</StyledButton>
- </div>
- );
- }
复制代码
Tailwind CSS是一个实用程序优先的CSS框架,它提供了低级别的实用程序类来构建自定义设计。
- npm install tailwindcss postcss autoprefixer
- npx tailwindcss init -p
复制代码- // tailwind.config.js
- module.exports = {
- content: [
- "./src/**/*.{js,jsx,ts,tsx}",
- ],
- theme: {
- extend: {},
- },
- plugins: [],
- }
复制代码- /* src/index.css */
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
复制代码- import React from 'react';
- function Button() {
- return (
- <div>
- <button className="bg-white text-green-500 border-2 border-green-500 px-8 py-4 text-base m-2 rounded cursor-pointer hover:bg-gray-100">
- Normal Button
- </button>
- <button className="bg-green-500 text-white border-2 border-green-500 px-8 py-4 text-base m-2 rounded cursor-pointer hover:bg-green-600">
- Primary Button
- </button>
- </div>
- );
- }
复制代码
测试
测试是确保应用质量和稳定性的重要环节。React应用通常使用Jest和React Testing Library进行测试。
Create React App已经内置了Jest和React Testing Library,所以不需要额外安装。如果你使用其他构建工具,可以手动安装:
- npm install --save-dev jest @testing-library/react @testing-library/jest-dom
复制代码- // Button.js
- import React from 'react';
- function Button({ onClick, children }) {
- return (
- <button onClick={onClick}>
- {children}
- </button>
- );
- }
- export default Button;
复制代码- // Button.test.js
- import React from 'react';
- import { render, screen, fireEvent } from '@testing-library/react';
- import Button from './Button';
- test('renders button with text', () => {
- render(<Button>Click Me</Button>);
- const buttonElement = screen.getByText(/Click Me/i);
- expect(buttonElement).toBeInTheDocument();
- });
- test('calls onClick when button is clicked', () => {
- const handleClick = jest.fn();
- render(<Button onClick={handleClick}>Click Me</Button>);
-
- const buttonElement = screen.getByText(/Click Me/i);
- fireEvent.click(buttonElement);
-
- expect(handleClick).toHaveBeenCalledTimes(1);
- });
复制代码- // UserList.js
- import React, { useState, useEffect } from 'react';
- import axios from 'axios';
- function UserList() {
- const [users, setUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- useEffect(() => {
- axios.get('https://jsonplaceholder.typicode.com/users')
- .then(response => {
- setUsers(response.data);
- setLoading(false);
- });
- }, []);
- if (loading) return <div>Loading...</div>;
- return (
- <div>
- <h1>User List</h1>
- <ul>
- {users.map(user => (
- <li key={user.id}>
- {user.name} - {user.email}
- </li>
- ))}
- </ul>
- </div>
- );
- }
- export default UserList;
复制代码- // UserList.test.js
- import React from 'react';
- import { render, screen, waitFor } from '@testing-library/react';
- import axios from 'axios';
- import UserList from './UserList';
- // Mock axios
- jest.mock('axios');
- test('renders user list after loading', async () => {
- // Mock API response
- const mockUsers = [
- { id: 1, name: 'John Doe', email: 'john@example.com' },
- { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
- ];
-
- axios.get.mockResolvedValue({ data: mockUsers });
-
- render(<UserList />);
-
- // Initially should show loading
- expect(screen.getByText(/Loading.../i)).toBeInTheDocument();
-
- // Wait for the user list to be rendered
- await waitFor(() => {
- expect(screen.getByText(/John Doe/i)).toBeInTheDocument();
- expect(screen.getByText(/jane@example.com/i)).toBeInTheDocument();
- });
- });
复制代码- // useCounter.js
- import { useState } from 'react';
- function useCounter(initialValue = 0) {
- const [count, setCount] = useState(initialValue);
-
- const increment = () => setCount(count + 1);
- const decrement = () => setCount(count - 1);
- const reset = () => setCount(initialValue);
-
- return { count, increment, decrement, reset };
- }
- export default useCounter;
复制代码- // useCounter.test.js
- import { renderHook, act } from '@testing-library/react-hooks';
- import useCounter from './useCounter';
- test('should increment counter', () => {
- const { result } = renderHook(() => useCounter());
-
- act(() => {
- result.current.increment();
- });
-
- expect(result.current.count).toBe(1);
- });
- test('should decrement counter', () => {
- const { result } = renderHook(() => useCounter());
-
- act(() => {
- result.current.decrement();
- });
-
- expect(result.current.count).toBe(-1);
- });
- test('should reset counter', () => {
- const { result } = renderHook(() => useCounter(5));
-
- act(() => {
- result.current.increment();
- result.current.increment();
- });
-
- expect(result.current.count).toBe(7);
-
- act(() => {
- result.current.reset();
- });
-
- expect(result.current.count).toBe(5);
- });
复制代码
性能优化
随着应用规模的增长,性能优化变得越来越重要。React提供了多种优化技术来提高应用的性能。
React.memo是一个高阶组件,它通过记忆化组件的渲染结果来优化性能。如果组件的props没有变化,React将跳过渲染组件并复用上次渲染的结果。
- import React from 'react';
- // 普通组件
- const ExpensiveComponent = ({ data }) => {
- console.log('ExpensiveComponent rendered');
- return (
- <div>
- {data.map(item => (
- <div key={item.id}>{item.name}</div>
- ))}
- </div>
- );
- };
- // 使用React.memo优化
- const MemoizedExpensiveComponent = React.memo(({ data }) => {
- console.log('MemoizedExpensiveComponent rendered');
- return (
- <div>
- {data.map(item => (
- <div key={item.id}>{item.name}</div>
- ))}
- </div>
- );
- });
- function App() {
- const [count, setCount] = React.useState(0);
- const [data, setData] = React.useState([
- { id: 1, name: 'Item 1' },
- { id: 2, name: 'Item 2' },
- { id: 3, name: 'Item 3' }
- ]);
- return (
- <div>
- <h1>Count: {count}</h1>
- <button onClick={() => setCount(count + 1)}>Increment</button>
-
- <h2>Expensive Component</h2>
- <ExpensiveComponent data={data} />
-
- <h2>Memoized Expensive Component</h2>
- <MemoizedExpensiveComponent data={data} />
- </div>
- );
- }
复制代码
useMemo用于缓存计算结果,避免在每次渲染时都进行昂贵的计算。
- import React from 'react';
- function ExpensiveCalculation({ numbers }) {
- // 没有使用useMemo,每次渲染都会重新计算
- const sumWithoutMemo = numbers.reduce((acc, num) => acc + num, 0);
-
- // 使用useMemo,只有numbers变化时才重新计算
- const sumWithMemo = React.useMemo(() => {
- console.log('Expensive calculation performed');
- return numbers.reduce((acc, num) => acc + num, 0);
- }, [numbers]);
- return (
- <div>
- <p>Sum without memo: {sumWithoutMemo}</p>
- <p>Sum with memo: {sumWithMemo}</p>
- </div>
- );
- }
- function App() {
- const [count, setCount] = React.useState(0);
- const [numbers, setNumbers] = React.useState([1, 2, 3, 4, 5]);
- return (
- <div>
- <h1>Count: {count}</h1>
- <button onClick={() => setCount(count + 1)}>Increment</button>
-
- <ExpensiveCalculation numbers={numbers} />
-
- <button onClick={() => setNumbers([...numbers, numbers.length + 1])}>
- Add Number
- </button>
- </div>
- );
- }
复制代码
useCallback用于缓存函数,避免在每次渲染时都创建新的函数实例。这在将函数作为props传递给优化过的子组件时特别有用。
- import React from 'react';
- // 使用React.memo优化的按钮组件
- const Button = React.memo(({ onClick, children }) => {
- console.log(`Button "${children}" rendered`);
- return <button onClick={onClick}>{children}</button>;
- });
- function App() {
- const [count, setCount] = React.useState(0);
-
- // 没有使用useCallback,每次渲染都会创建新的函数
- const incrementWithoutCallback = () => {
- setCount(count + 1);
- };
-
- // 使用useCallback,只有依赖变化时才创建新函数
- const incrementWithCallback = React.useCallback(() => {
- setCount(count + 1);
- }, [count]);
- return (
- <div>
- <h1>Count: {count}</h1>
-
- <Button onClick={incrementWithoutCallback}>
- Increment without useCallback
- </Button>
-
- <Button onClick={incrementWithCallback}>
- Increment with useCallback
- </Button>
- </div>
- );
- }
复制代码
代码分割允许你将应用分割成多个包,按需加载,减少初始加载时间。
- import React, { Suspense } from 'react';
- // 使用React.lazy懒加载组件
- const LazyComponent = React.lazy(() => import('./LazyComponent'));
- function App() {
- return (
- <div>
- <h1>My App</h1>
-
- {/* 使用Suspense包裹懒加载的组件 */}
- <Suspense fallback={<div>Loading...</div>}>
- <LazyComponent />
- </Suspense>
- </div>
- );
- }
复制代码- import React, { Suspense } from 'react';
- import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
- // 懒加载页面组件
- const Home = React.lazy(() => import('./Home'));
- const About = React.lazy(() => import('./About'));
- const Contact = React.lazy(() => import('./Contact'));
- function App() {
- return (
- <Router>
- <div>
- <nav>
- <ul>
- <li><Link to="/">Home</Link></li>
- <li><Link to="/about">About</Link></li>
- <li><Link to="/contact">Contact</Link></li>
- </ul>
- </nav>
- <Suspense fallback={<div>Loading...</div>}>
- <Routes>
- <Route path="/" element={<Home />} />
- <Route path="/about" element={<About />} />
- <Route path="/contact" element={<Contact />} />
- </Routes>
- </Suspense>
- </div>
- </Router>
- );
- }
复制代码
对于包含大量数据的列表,可以使用虚拟化技术只渲染可见区域的元素,提高性能。
- import React from 'react';
- import { FixedSizeList as List } from 'react-window';
- // 生成大量数据
- const generateItems = (count) => {
- return Array.from({ length: count }, (_, index) => ({
- id: index,
- name: `Item ${index + 1}`,
- description: `Description for item ${index + 1}`
- }));
- };
- const items = generateItems(10000);
- // 行组件
- const Row = ({ index, style }) => (
- <div style={style} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'}>
- <h3>{items[index].name}</h3>
- <p>{items[index].description}</p>
- </div>
- );
- function VirtualizedList() {
- return (
- <div>
- <h1>Virtualized List</h1>
- <List
- height={500}
- itemCount={items.length}
- itemSize={100}
- width="100%"
- >
- {Row}
- </List>
- </div>
- );
- }
复制代码
部署
开发完成后,需要将React应用部署到服务器上,使其可以通过互联网访问。
使用Create React App创建的项目,可以通过以下命令构建生产版本:
这将在项目根目录下创建一个build文件夹,包含优化后的生产版本代码。
1. 将构建后的build文件夹上传到GitHub仓库。
2. 登录Netlify,点击”New site from Git”。
3. 选择你的GitHub仓库。
4. 构建设置:Build command:npm run buildPublish directory:build
5. Build command:npm run build
6. Publish directory:build
7. 点击”Deploy site”。
• Build command:npm run build
• Publish directory:build
1. 将构建后的build文件夹上传到GitHub仓库。
2. 登录Vercel,点击”New Project”。
3. 选择你的GitHub仓库。
4. 构建设置:Build command:npm run buildOutput directory:build
5. Build command:npm run build
6. Output directory:build
7. 点击”Deploy”。
• Build command:npm run build
• Output directory:build
1. 安装gh-pages包:
- npm install gh-pages --save-dev
复制代码
1. 在package.json中添加部署脚本:
- "scripts": {
- "predeploy": "npm run build",
- "deploy": "gh-pages -d build"
- }
复制代码
1. 添加homepage字段到package.json:
- "homepage": "https://yourusername.github.io/your-repo-name"
复制代码
1. 运行部署命令:
如果你需要将React应用作为Node.js应用的一部分部署,可以使用Express服务器:
- // server.js
- const express = require('express');
- const path = require('path');
- const app = express();
- // 静态文件服务
- app.use(express.static(path.join(__dirname, 'build')));
- // 所有路由都返回index.html
- app.get('*', (req, res) => {
- res.sendFile(path.join(__dirname, 'build', 'index.html'));
- });
- const port = process.env.PORT || 3000;
- app.listen(port, () => {
- console.log(`Server is running on port ${port}`);
- });
复制代码- // package.json
- {
- "name": "my-react-app",
- "version": "1.0.0",
- "scripts": {
- "start": "node server.js",
- "build": "react-scripts build",
- "test": "react-scripts test",
- "eject": "react-scripts eject"
- },
- "dependencies": {
- "express": "^4.17.1"
- },
- "devDependencies": {
- "react-scripts": "4.0.3"
- }
- }
复制代码
创建Dockerfile:
- # 构建阶段
- FROM node:16 as build
- WORKDIR /app
- COPY package*.json ./
- RUN npm install
- COPY . .
- RUN npm run build
- # 生产阶段
- FROM nginx:alpine
- COPY --from=build /app/build /usr/share/nginx/html
- EXPOSE 80
- CMD ["nginx", "-g", "daemon off;"]
复制代码
构建Docker镜像:
- docker build -t my-react-app .
复制代码
运行Docker容器:
- docker run -p 80:80 my-react-app
复制代码
最佳实践与常见陷阱
1. 组件设计保持组件小而专注,每个组件只做一件事将UI组件和容器组件分离使用函数组件和Hooks,除非必须使用类组件
2. 保持组件小而专注,每个组件只做一件事
3. 将UI组件和容器组件分离
4. 使用函数组件和Hooks,除非必须使用类组件
5. 状态管理将状态放在最近的共同祖先组件中对于复杂应用,使用Redux或Context API进行全局状态管理避免不必要的状态提升
6. 将状态放在最近的共同祖先组件中
7. 对于复杂应用,使用Redux或Context API进行全局状态管理
8. 避免不必要的状态提升
9. 性能优化使用React.memo、useMemo和useCallback优化性能对大型列表使用虚拟化使用代码分割减少初始加载时间
10. 使用React.memo、useMemo和useCallback优化性能
11. 对大型列表使用虚拟化
12. 使用代码分割减少初始加载时间
13. 代码组织按功能或路由组织文件,而不是按类型使用绝对路径导入保持一致的命名约定
14. 按功能或路由组织文件,而不是按类型
15. 使用绝对路径导入
16. 保持一致的命名约定
17. 测试为组件编写单元测试和集成测试测试用户交互,而不是实现细节使用React Testing Library进行测试
18. 为组件编写单元测试和集成测试
19. 测试用户交互,而不是实现细节
20. 使用React Testing Library进行测试
组件设计
• 保持组件小而专注,每个组件只做一件事
• 将UI组件和容器组件分离
• 使用函数组件和Hooks,除非必须使用类组件
状态管理
• 将状态放在最近的共同祖先组件中
• 对于复杂应用,使用Redux或Context API进行全局状态管理
• 避免不必要的状态提升
性能优化
• 使用React.memo、useMemo和useCallback优化性能
• 对大型列表使用虚拟化
• 使用代码分割减少初始加载时间
代码组织
• 按功能或路由组织文件,而不是按类型
• 使用绝对路径导入
• 保持一致的命名约定
测试
• 为组件编写单元测试和集成测试
• 测试用户交互,而不是实现细节
• 使用React Testing Library进行测试
1. - 直接修改状态“`jsx
- // 错误
- this.state.count = 1;
复制代码
// 正确
this.setState({ count: 1 });
// 函数组件中
// 错误
const [count, setCount] = useState(0);
count = 1;
// 正确
setCount(1);
- 2. **在条件语句中使用Hooks**
- ```jsx
- // 错误
- if (condition) {
- const [state, setState] = useState(0);
- }
-
- // 正确
- const [state, setState] = useState(0);
- if (condition) {
- // 使用state
- }
复制代码
1. - 在循环中创建组件“`jsx
- // 错误
- function List({ items }) {
- return ({items.map(item => {
- const [value, setValue] = useState(0);
- return ({item.name}: {value}setValue(value + 1)}>Increment);
- })});
- }
复制代码
// 正确 - 提取为单独的组件
function ListItem({ item }) {
- const [value, setValue] = useState(0);
- return (
- <div>
- {item.name}: {value}
- <button onClick={() => setValue(value + 1)}>Increment</button>
- </div>
- );
复制代码
}
function List({ items }) {
- return (
- <div>
- {items.map(item => (
- <ListItem key={item.id} item={item} />
- ))}
- </div>
- );
复制代码
}
- 4. **过度使用状态**
- ```jsx
- // 错误 - 不必要的状态
- function UserProfile({ user }) {
- const [name, setName] = useState(user.name);
- const [email, setEmail] = useState(user.email);
-
- return (
- <div>
- <h1>{name}</h1>
- <p>{email}</p>
- </div>
- );
- }
-
- // 正确 - 直接使用props
- function UserProfile({ user }) {
- return (
- <div>
- <h1>{user.name}</h1>
- <p>{user.email}</p>
- </div>
- );
- }
复制代码
1. - 忘记依赖数组“`jsx
- // 错误 - 缺少依赖
- useEffect(() => {
- const timer = setInterval(() => {
- setCount(count + 1);
- }, 1000);return () => clearInterval(timer);
- }, []); // 缺少count依赖
复制代码
忘记依赖数组“`jsx
// 错误 - 缺少依赖
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, []); // 缺少count依赖
// 正确 - 包含所有依赖
useEffect(() => {
- const timer = setInterval(() => {
- setCount(count + 1);
- }, 1000);
- return () => clearInterval(timer);
复制代码
}, [count]); // 包含count依赖
// 或者使用函数式更新避免依赖
useEffect(() => {
- const timer = setInterval(() => {
- setCount(prevCount => prevCount + 1);
- }, 1000);
- return () => clearInterval(timer);
复制代码
}, []); // 不需要依赖
“`
总结与展望
React作为一个强大的前端框架,已经发展多年并持续演进。从最初的类组件到现在的函数组件和Hooks,React不断提供更好的开发体验和性能优化。
本教程涵盖了React的核心概念、实际应用和最佳实践,包括:
• JSX语法和组件概念
• Props和State管理
• 生命周期和Hooks
• 路由与导航
• 状态管理(Redux、MobX)
• 与后端交互
• 样式处理
• 测试
• 性能优化
• 部署
• 最佳实践与常见陷阱
随着React生态系统的发展,我们可以期待更多创新和改进。React 18已经引入了并发渲染、自动批处理、Suspense等新特性,这些将进一步改善用户体验和开发体验。
要成为一名优秀的React开发者,除了掌握框架本身,还需要了解JavaScript的最新特性、Web API、性能优化技巧以及整个前端生态系统。持续学习和实践是关键。
希望本教程能够帮助你入门React开发,并在你的项目中应用这些知识。祝你编码愉快! |
|