React.js组件开发全攻略:从基础搭建到状态管理实战

一、从0到1构建React组件:基础逻辑与拆分原则

React的核心是“组件化”——把UI拆成独立、可复用的单元。现在函数组件是绝对主流(类组件已逐步退出核心场景),我们从最基础的函数组件写起:

React.js组件开发全攻略:从基础搭建到状态管理实战

比如要做一个“用户信息卡片”,先写一个最基础的函数组件:

function UserCard() {
  return (
    <div style={{ border: '1px solid #eee', padding: '16px', borderRadius: '8px' }}>
      <img src="https://via.placeholder.com/80" alt="头像" style={{ borderRadius: '50%' }} />
      <h3>张三</h3>
      <p>前端开发者 | 热爱React</p>
    </div>
  );
}

这个组件能显示用户信息,但如果要复用(比如显示不同用户),就得通过props传递数据——把动态内容从外部传入:

// 优化后:接收props动态渲染
function UserCard({ avatar, name, role }) {
  return (
    <div style={{ border: '1px solid #eee', padding: '16px', borderRadius: '8px' }}>
      <img src={avatar} alt={name} style={{ borderRadius: '50%' }} />
      <h3>{name}</h3>
      <p>{role} | 热爱React</p>
    </div>
  );
}

// 使用时传递不同数据
function App() {
  return (
    <div>
      <UserCard 
        avatar="https://via.placeholder.com/80" 
        name="张三" 
        role="前端开发者" 
      />
      <UserCard 
        avatar="https://via.placeholder.com/80" 
        name="李四" 
        role="后端开发者" 
      />
    </div>
  );
}

组件拆分的核心原则是单一职责:一个组件只做一件事。比如“用户列表”可以拆成UserList(负责列表容器)和UserItem(负责单个用户项),这样修改单个用户的样式时,不会影响整个列表的逻辑。

二、局部状态管理:useState的正确打开方式

组件内部的状态(比如输入框内容、按钮点击次数)用useState管理——这是React最基础的状态工具。举个“计数器”的经典例子:

import { useState } from 'react';

function Counter() {
  // 解构出状态值count和更新函数setCount,初始值为0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>你点击了{count}次</p>
      {/* 点击时更新count:注意setCount是异步的,不能直接依赖当前count的值做计算 */}
      <button onClick={() => setCount(prev => prev + 1)}>点我加1</button>
    </div>
  );
}

这里有个避坑点setCount是异步更新,如果你直接写setCount(count + 1),在连续点击时可能会丢失状态(比如快速点两次,结果只加1)。解决方法是用函数式更新(传入prev参数),确保每次取到的是最新状态。

三、跨组件通信:从props到useContext的进阶

当组件层级变深(比如爷爷→爸爸→儿子→孙子),用props一层一层传递数据会很麻烦(俗称“props drilling”)。这时候用useContext解决——它能让数据在组件树中“全局”可用,无需手动传递。

比如做一个“主题切换”功能,让所有组件都能获取当前主题:
1. 创建上下文:定义主题的默认值和操作方法

import { createContext, useState, useContext } from 'react';

// 1. 创建ThemeContext,默认值为light主题
const ThemeContext = createContext({
  theme: 'light',
  toggleTheme: () => {}
});

// 2. 创建Provider组件,负责管理主题状态
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  // 切换主题的方法
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  // 把状态和方法传递给子组件
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 3. 封装自定义Hook,简化使用(可选,但更优雅)
export function useTheme() {
  return useContext(ThemeContext);
}
  1. 在根组件包裹Provider:让所有子组件都能访问主题

    // index.js
    import { ThemeProvider } from './ThemeContext';
    import App from './App';
    
    ReactDOM.render(
      <ThemeProvider>
        <App />
      </ThemeProvider>,
      document.getElementById('root')
    );
    
  2. 在任意组件中使用主题:不用传递props,直接用useTheme获取

    import { useTheme } from './ThemeContext';
    
    function ThemedButton() {
      const { theme, toggleTheme } = useTheme();
    
      return (
        <button 
          onClick={toggleTheme}
          style={{
            background: theme === 'light' ? '#fff' : '#333',
            color: theme === 'light' ? '#333' : '#fff',
            padding: '8px 16px',
            border: 'none',
            borderRadius: '4px'
          }}
        >
          当前主题:{theme}(点击切换)
        </button>
      );
    }
    

四、复杂状态管理:Redux Toolkit的实战技巧

当应用变大(比如有购物车、用户登录状态、商品列表),useContext会不够用——因为它无法高效处理“跨模块的状态共享”和“状态变更日志”。这时候用Redux Toolkit(官方推荐的Redux简化方案)。

以“购物车”为例,步骤如下:
1. 安装依赖

npm install @reduxjs/toolkit react-redux
  1. 创建Slice:Slice是Redux的“模块”,包含状态、 reducer和action

    // features/cart/cartSlice.js
    import { createSlice } from '@reduxjs/toolkit';
    
    // 初始状态:空购物车
    const initialState = {
      items: []
    };
    
    // 创建cartSlice:name是模块名,initialState是初始状态,reducers是状态变更逻辑
    const cartSlice = createSlice({
      name: 'cart',
      initialState,
      reducers: {
        // 添加商品:state是当前状态,action是传入的数据(action.payload是商品信息)
        addItem: (state, action) => {
          state.items.push(action.payload); // Redux Toolkit允许直接修改state(内部用了Immer库)
        },
        // 删除商品:根据id过滤掉要删除的项
        removeItem: (state, action) => {
          state.items = state.items.filter(item => item.id !== action.payload);
        }
      }
    });
    
    // 导出action creator(用于组件中触发状态变更)
    export const { addItem, removeItem } = cartSlice.actions;
    
    // 导出reducer(用于配置store)
    export default cartSlice.reducer;
    
  2. 创建Store:整合所有Slice的reducer

    // app/store.js
    import { configureStore } from '@reduxjs/toolkit';
    import cartReducer from '../features/cart/cartSlice';
    
    // 创建store:reducer对象中的键是模块名,值是对应的reducer
    export const store = configureStore({
      reducer: {
        cart: cartReducer
      }
    });
    
  3. 在根组件包裹Redux Provider

    // index.js
    import { Provider } from 'react-redux';
    import { store } from './app/store';
    import App from './App';
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    
  4. 在组件中使用Redux:用useSelector取状态,useDispatch触发action

    import { useSelector, useDispatch } from 'react-redux';
    import { addItem, removeItem } from './features/cart/cartSlice';
    
    function Cart() {
      // 从Redux store中取购物车 items
      const cartItems = useSelector(state => state.cart.items);
      // 获取dispatch函数,用于触发action
      const dispatch = useDispatch();
    
      // 模拟商品数据
      const product = { id: 1, name: 'React实战教程', price: 99 };
    
      return (
        <div>
          <h3>购物车({cartItems.length}件)</h3>
          <button onClick={() => dispatch(addItem(product))}>添加商品</button>
          <ul>
            {cartItems.map(item => (
              <li key={item.id}>
                {item.name} - {item.price}元
                <button onClick={() => dispatch(removeItem(item.id))}>删除</button>
              </li>
            ))}
          </ul>
        </div>
      );
    }
    

四、状态管理的选择指南:到底用哪个?

很多开发者会困惑:useStateuseContextRedux Toolkit到底怎么选?给你一张决策表

场景 工具选择
组件内部的局部状态 useState
层级较浅的跨组件通信 props/useContext
层级很深或全局状态 useContext
复杂应用(多模块、多状态) Redux Toolkit

五、组件开发的最佳实践

  1. 状态尽可能局部化:能放在子组件的状态,就不要提到父组件(比如输入框的内容,放在输入框组件内部比放在父组件更合理)。
  2. 用自定义Hook复用逻辑:把重复的状态逻辑抽成Hook,比如“获取窗口大小”:
    import { useState, useEffect } from 'react';
    
    export function useWindowSize() {
      const [size, setSize] = useState({
        width: window.innerWidth,
        height: window.innerHeight
      });
    
      useEffect(() => {
        const handleResize = () => {
          setSize({
            width: window.innerWidth,
            height: window.innerHeight
          });
        };
        window.addEventListener('resize', handleResize);
        // 组件卸载时移除事件监听
        return () => window.removeEventListener('resize', handleResize);
      }, []);
    
      return size;
    }
    
    // 使用时直接调用Hook
    function WindowSize() {
      const { width, height } = useWindowSize();
      return <p>窗口大小:{width}x{height}</p>;
    }
    
  3. 避免不必要的重新渲染:用React.memo包裹纯组件,防止因父组件重新渲染而不必要更新:
    // 用React.memo包裹子组件,只有props变化时才重新渲染
    const MemoizedUserCard = React.memo(UserCard);
    

原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/180

(0)

相关推荐