React笔记

145 阅读5分钟

React Hooks

1. useState

用于在函数组件中添加状态管理功能。它让函数组件能够拥有自己的内部状态。

(1)基本语法

const [state, setState] = useState(initialValue);
  • state: 当前状态值
  • setState: 更新状态的函数
  • initialValue: 状态的初始值

(2)重要特性

  • 状态更新是异步的
function AsyncExample() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    console.log('点击前:', count); // 0
    setCount(count + 1);
    console.log('点击后:', count); // 仍然是 0,因为状态更新是异步的
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>增加</button>
    </div>
  );
}
  • 函数式更新:当新状态依赖于前一个状态时,需使用函数式更新:
function Counter() {
  const [count, setCount] = useState(0);
  
  const incrementTwice = () => {
    // 错误方式:可能不会按预期工作
    // setCount(count + 1);
    // setCount(count + 1);
    
    // 正确方式:使用函数式更新
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementTwice}>增加2</button>
    </div>
  );
}
  • React 会对多个状态更新进行批处理:
function BatchingExample() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  
  const handleClick = () => {
    // 这些更新会被批处理,只触发一次重新渲染
    setCount(c => c + 1);
    setFlag(f => !f);
  };
  
  console.log('渲染'); // 只会打印一次
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Flag: {flag.toString()}</p>
      <button onClick={handleClick}>更新</button>
    </div>
  );
}

2. useEffect

用于处理副作用,包括:请求数据、设置订阅、操作 DOM、定时器、清理资源。

(1)基本语法

useEffect(() => {
  console.log('组件挂载或更新');
  return () => {
    console.log('组件卸载或清理');
  };
}, [count]); // 依赖项改变时才执行

第二个参数是依赖数组: 空数组 []时,只在挂载时执行一次;有依赖项时,依赖项变化时执行;无依赖数组时,每次渲染都执行。

(2)在return 中,可以取消请求、清理定时器、移除事件监听器

// 取消请求
function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);
  
  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }
    
    const abortController = new AbortController();
    
    const searchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/search?q=${query}`, {
          signal: abortController.signal
        });
        const data = await response.json();
        setResults(data);
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('搜索失败:', error);
        }
      } finally {
        setLoading(false);
      }
    };
    
    searchData();
    
    // 清理函数:取消请求
    return () => {
      abortController.abort();
    };
  }, [query]);
  
  return (
    <div>
      {loading && <div>搜索中...</div>}
      <ul>
        {results.map(item => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}

3. useContext

用于在组件树中跨层级传递数据,避免了逐层传递 props的问题。能够在不通过 props 的情况下,将数据传递给深层嵌套的组件。

  • Context: 上下文对象,用于存储共享数据
  • Provider: 提供者组件,用于提供数据
  • Consumer: 消费者组件,用于使用数据
import React, { createContext, useContext, useState } from 'react';
​
// 1. 创建 Context
const ThemeContext = createContext();
​
// 2. 创建 Provider 组件
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}
​
// 3. 在子组件中使用 Context
function Header() {
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  return (
    <header style={{ 
      backgroundColor: theme === 'light' ? '#fff' : '#333',
      color: theme === 'light' ? '#333' : '#fff'
    }}>
      <h1>我的应用</h1>
      <button onClick={toggleTheme}>
        切换到 {theme === 'light' ? '深色' : '浅色'} 模式
      </button>
    </header>
  );
}
​
function Content() {
  const { theme } = useContext(ThemeContext);
  
  return (
    <main style={{ 
      backgroundColor: theme === 'light' ? '#f5f5f5' : '#222',
      color: theme === 'light' ? '#333' : '#fff',
      padding: '20px'
    }}>
      <p>当前主题: {theme}</p>
    </main>
  );
}
​
// 4. 在应用中使用
function App() {
  return (
    <ThemeProvider>
      <Header />
      <Content />
    </ThemeProvider>
  );
}

4. useRef

用于获取 DOM 节点或保存可变值,useRef 返回一个对象,该对象有一个 current 属性,可以通过修改 current 来存储任何值。与 useState 不同的是,修改 useRef 的值不会触发组件重新渲染。

function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  );
}

5. useMemo

可以缓存计算结果,只有在依赖项发生变化时才会重新计算。

(1)基本语法

import { useMemo } from 'react';
​
const memoizedValue = useMemo(() => {
  return expensiveCalculation(a, b);
}, [a, b]);

useMemo 接收两个参数:

  • 计算函数:返回需要缓存的值
  • 依赖数组:当数组中的值发生变化时,才会重新执行计算函数

6. useCallback

用于缓存函数,返回一个记忆化的回调函数,只有在依赖项发生变化时才会重新创建函数。

import { useCallback } from 'react';
​
const memoizedCallback = useCallback(() => {
  // 函数逻辑
}, [dependency1, dependency2]);

useCallback 接收两个参数:

  • 回调函数:需要缓存的函数
  • 依赖数组:当数组中的值发生变化时,才会重新创建函数

7. useReducer

useState 的替代方案,特别适用于复杂的状态逻辑管理。

import { useReducer } from 'react';
const [state, dispatch] = useReducer(reducer, initialState);
function Counter() {
  const initialState = { count: 0 };
  
  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return { count: state.count + 1 };
      case 'decrement':
        return { count: state.count - 1 };
      case 'reset':
        return initialState;
      default:
        throw new Error();
    }
  }
  
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <div>
      <p>计数: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </div>
  );
}

Redux 与 React 使用指南

Redux 简介

Redux 是一个可预测的状态管理库,专为 JavaScript 应用程序设计。它帮助您管理应用程序的状态,使状态变化可预测、可调试。

为什么需要 Redux?

在大型 React 应用中,组件间的数据传递变得复杂:

  • 多层组件需要共享状态
  • 状态提升导致 props 层层传递
  • 组件状态管理混乱
  • 调试困难

Redux 通过集中式状态管理解决这些问题。

Redux 核心概念

三大原则

  1. 单一数据源:整个应用的状态存储在一个 store 的对象树中
  2. 状态只读:改变状态的唯一方法是触发 action
  3. 纯函数执行修改:使用纯函数 reducer 来执行状态修改

核心组件

Store:保存应用状态的容器

  • getState():获取当前状态
  • dispatch(action):分发 action
  • subscribe(listener):注册状态变化监听器

Action:描述发生什么的普通对象

{
  type: 'ADD_TODO',
  payload: { id: 1, text: '学习 Redux' }
}

Reducer:纯函数,描述 action 如何改变状态

(state, action) => newState

在 React 中使用 Redux

安装依赖

# 传统 Redux
npm install redux react-redux

# 推荐:Redux Toolkit
npm install @reduxjs/toolkit react-redux

基础示例:计数器应用

1. 创建 Store 和 Reducer
// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    }
  }
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
2. 在应用中提供 Store
// App.jsx
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './store';
import Counter from './components/Counter';

function App() {
  return (
    <Provider store={store}>
      <div className="App">
        <h1>Redux Counter App</h1>
        <Counter />
      </div>
    </Provider>
  );
}

export default App;
3. 在组件中使用 Redux
// components/Counter.jsx
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount } from '../store/counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  const [incrementAmount, setIncrementAmount] = useState('2');

  return (
    <div>
      <div>
        <button onClick={() => dispatch(increment())}>+</button>
        <span>{count}</span>
        <button onClick={() => dispatch(decrement())}>-</button>
      </div>
      <div>
        <input
          value={incrementAmount}
          onChange={(e) => setIncrementAmount(e.target.value)}
        />
        <button
          onClick={() => dispatch(incrementByAmount(Number(incrementAmount) || 0))}
        >
          Add Amount
        </button>
      </div>
    </div>
  );
}

export default Counter;

完整示例:Todo 应用

1. 创建 Todo Slice
// store/todoSlice.js
import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    filter: 'all' // all, active, completed
  },
  reducers: {
    addTodo: (state, action) => {
      state.items.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.items.find(item => item.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    deleteTodo: (state, action) => {
      state.items = state.items.filter(item => item.id !== action.payload);
    },
    setFilter: (state, action) => {
      state.filter = action.payload;
    },
    clearCompleted: (state) => {
      state.items = state.items.filter(item => !item.completed);
    }
  }
});

export const { addTodo, toggleTodo, deleteTodo, setFilter, clearCompleted } = todoSlice.actions;
export default todoSlice.reducer;

// Selectors
export const selectTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;
export const selectFilteredTodos = (state) => {
  const todos = selectTodos(state);
  const filter = selectFilter(state);
  
  switch (filter) {
    case 'active':
      return todos.filter(todo => !todo.completed);
    case 'completed':
      return todos.filter(todo => todo.completed);
    default:
      return todos;
  }
};
2. Todo 组件
// components/TodoApp.jsx
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  addTodo,
  toggleTodo,
  deleteTodo,
  setFilter,
  clearCompleted,
  selectFilteredTodos,
  selectFilter
} from '../store/todoSlice';

function TodoApp() {
  const [inputValue, setInputValue] = useState('');
  const todos = useSelector(selectFilteredTodos);
  const filter = useSelector(selectFilter);
  const dispatch = useDispatch();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (inputValue.trim()) {
      dispatch(addTodo(inputValue.trim()));
      setInputValue('');
    }
  };

  return (
    <div>
      <h2>Todo List</h2>
      
      {/* 添加 Todo */}
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="添加新任务..."
        />
        <button type="submit">添加</button>
      </form>

      {/* 过滤器 */}
      <div>
        <button
          onClick={() => dispatch(setFilter('all'))}
          className={filter === 'all' ? 'active' : ''}
        >
          全部
        </button>
        <button
          onClick={() => dispatch(setFilter('active'))}
          className={filter === 'active' ? 'active' : ''}
        >
          未完成
        </button>
        <button
          onClick={() => dispatch(setFilter('completed'))}
          className={filter === 'completed' ? 'active' : ''}
        >
          已完成
        </button>
        <button onClick={() => dispatch(clearCompleted())}>
          清除已完成
        </button>
      </div>

      {/* Todo 列表 */}
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => dispatch(toggleTodo(todo.id))}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => dispatch(deleteTodo(todo.id))}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoApp;
3. 更新 Store 配置
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import todoReducer from './todoSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    todos: todoReducer
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

异步操作处理

使用 createAsyncThunk

// store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 异步 thunk
export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, { rejectWithValue }) => {
    try {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) {
        throw new Error('Failed to fetch user');
      }
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null
  },
  reducers: {
    clearUser: (state) => {
      state.data = null;
      state.error = null;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  }
});

export const { clearUser } = userSlice.actions;
export default userSlice.reducer;

在组件中使用异步操作

// components/UserProfile.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser, clearUser } from '../store/userSlice';

function UserProfile({ userId }) {
  const { data: user, loading, error } = useSelector(state => state.user);
  const dispatch = useDispatch();

  useEffect(() => {
    if (userId) {
      dispatch(fetchUser(userId));
    }
    return () => {
      dispatch(clearUser());
    };
  }, [dispatch, userId]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;
  if (!user) return <div>用户不存在</div>;

  return (
    <div>
      <h3>{user.name}</h3>
      <p>邮箱: {user.email}</p>
      <p>电话: {user.phone}</p>
    </div>
  );
}

export default UserProfile;

React-Redux Hooks

useSelector

用于从 Redux store 中提取数据

const count = useSelector(state => state.counter.value);
const todos = useSelector(state => state.todos.items);

// 使用 selector 函数
const completedTodos = useSelector(state => 
  state.todos.items.filter(todo => todo.completed)
);

useDispatch

用于获取 dispatch 函数

const dispatch = useDispatch();

// 分发 action
dispatch(increment());
dispatch(addTodo('新任务'));

自定义 Hooks(推荐)

// hooks/redux.js
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from '../store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

最佳实践

1. 状态规范化

// 好的状态结构
{
  users: {
    byId: {
      1: { id: 1, name: 'John' },
      2: { id: 2, name: 'Jane' }
    },
    allIds: [1, 2]
  }
}

// 避免嵌套数组
{
  posts: [
    {
      id: 1,
      comments: [/* 大量嵌套数据 */]
    }
  ]
}

2. 使用 Selector

// 创建可复用的 selector
export const selectTodoById = (state, todoId) =>
  state.todos.find(todo => todo.id === todoId);

export const selectCompletedTodos = createSelector(
  [state => state.todos],
  todos => todos.filter(todo => todo.completed)
);

3. 分割 Reducer

// 按功能模块分割
const rootReducer = combineReducers({
  auth: authReducer,
  posts: postsReducer,
  comments: commentsReducer,
  ui: uiReducer
});

4. 使用中间件

const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(logger),
});

调试工具

Redux DevTools

const store = configureStore({
  reducer: rootReducer,
  devTools: process.env.NODE_ENV !== 'production'
});

安装浏览器扩展:Redux DevTools Extension

何时使用 Redux

适合的场景

  • 应用状态复杂,多个组件需要共享状态
  • 状态更新逻辑复杂
  • 需要时间旅行调试
  • 需要持久化状态
  • 团队协作需要统一的状态管理规范

不适合的场景

  • 简单应用,状态简单
  • 只有少数组件需要共享状态
  • 学习成本超过带来的收益

总结

Redux 为 React 应用提供了强大的状态管理能力。通过遵循单向数据流和不可变更新的原则,Redux 让应用状态变得可预测和易于调试。

现代的 Redux Toolkit 大大简化了使用体验,减少了样板代码,是当前推荐的使用方式。结合 React-Redux 的 hooks API,能够轻松构建可维护的大型应用。

记住:不是所有应用都需要 Redux,选择合适的工具来解决实际问题才是最重要的。

Mobx

MobX是一个简单、可扩展的状态管理库,通过响应式编程让状态管理变得简单和直观。让我为你详细介绍一下。

MobX核心概念

1. 什么是MobX?

MobX通过透明的函数响应式编程使得状态管理变得简单和可扩展。它的核心理念是:任何源自应用状态的东西都应该自动地获得

2. 核心原理

MobX基于三个重要概念:

  • State(状态) :驱动应用的数据
  • Derivations(衍生) :从状态中派生出来的任何东西
  • Actions(动作) :改变状态的任何一段代码

3. 主要装饰器/API

@observable / makeObservable

将数据变为可观察的:

import { makeObservable, observable, action } from 'mobx';

class TodoStore {
  todos = [];
  
  constructor() {
    makeObservable(this, {
      todos: observable,
      addTodo: action
    });
  }
  
  addTodo = (text) => {
    this.todos.push({ text, completed: false });
  }
}
@observer

让React组件自动响应状态变化:

import { observer } from 'mobx-react-lite';

const TodoList = observer(() => {
  return (
    <ul>
      {todoStore.todos.map(todo => 
        <li key={todo.id}>{todo.text}</li>
      )}
    </ul>
  );
});
@computed / computed

计算属性,基于现有状态自动派生:

class TodoStore {
  get completedTodosCount() {
    return this.todos.filter(todo => todo.completed).length;
  }
}
@action

标记修改状态的方法:

class TodoStore {
  toggleTodo = action((id) => {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  });
}

MobX vs Redux

特性MobXRedux
学习曲线平缓,更直观陡峭,概念较多
代码量较少较多
样板代码很少很多
性能自动优化需要手动优化
调试MobX DevToolsRedux DevTools
时间旅行支持但不是默认原生支持
函数式编程面向对象函数式

使用示例

基础用法

// store.js
import { makeObservable, observable, action, computed } from 'mobx';

class CounterStore {
  count = 0;
  
  constructor() {
    makeObservable(this, {
      count: observable,
      increment: action,
      decrement: action,
      doubleCount: computed
    });
  }
  
  increment = () => {
    this.count++;
  }
  
  decrement = () => {
    this.count--;
  }
  
  get doubleCount() {
    return this.count * 2;
  }
}

export const counterStore = new CounterStore();
// Counter.jsx
import React from 'react';
import { observer } from 'mobx-react-lite';
import { counterStore } from './store';

const Counter = observer(() => {
  return (
    <div>
      <p>Count: {counterStore.count}</p>
      <p>Double: {counterStore.doubleCount}</p>
      <button onClick={counterStore.increment}>+</button>
      <button onClick={counterStore.decrement}>-</button>
    </div>
  );
});

export default Counter;