自学内容网 自学内容网

React 之 Redux =》 理解+应用

Redux基础介绍

一、概述

Redux是一个可预测的JavaScript应用程序状态管理容器。它主要用于JavaScript应用,特别是在React、Angular、Vue等框架中,但也可以与其他视图库一起使用。Redux遵循单向数据流的原则,使得应用的状态变化可预测,方便调试和维护。

二、元素组成

1. Action(动作)

  • 定义:是一个包含type属性的普通JavaScript对象,用于描述发生了什么事情。type通常是一个字符串常量,用于唯一标识这个动作。例如:
const ADD_TODO = 'ADD_TODO';
const addTodo = (text) => {
    return {
        type: ADD_TODO,
        payload: text
    };
}
  • 作用:它是改变状态的唯一途径,就像一个“指令”,告诉Redux需要进行什么样的操作。

2. Reducer(纯函数)

  • 定义:是一个纯函数,它接收两个参数,当前的state(应用的状态)和一个action。根据actiontype来决定如何更新state,并返回新的state。例如:
const initialState = {
    todos: []
};
const todoReducer = (state = initialState, action) => {
    switch (action.type) {
        case ADD_TODO:
            return {
               ...state,
                todos: [...state.todos, {text: action.payload, completed: false}]
            };
        default:
            return state;
    }
}
  • 特点
    • 纯函数特性:给定相同的输入(相同的stateaction),总是返回相同的输出。没有副作用,如不会修改传入的参数,不会进行异步操作,也不会直接操作DOM。
    • 不可变性:在更新state时,应该返回一个新的对象,而不是直接修改原有的state。这有助于追踪状态的变化,并且在React等视图层中可以更高效地检测组件是否需要重新渲染。

3. Store(仓库)

  • 定义:是一个对象,它将actionreducer联系在一起。通过createStore函数创建(在Redux核心库中)。例如:
import { createStore } from 'redux';
const store = createStore(todoReducer);
  • 功能
    • 保存状态:它存储着整个应用的状态树。可以通过store.getState()方法获取当前的状态。
    • 分发动作:提供store.dispatch(action)方法,用于将action发送给reducer,从而触发状态的更新。
    • 订阅更新:通过store.subscribe(listener)方法,可以订阅状态的变化。当状态更新时,会调用listener函数,通常用于更新UI等操作。

三、原理结构

Redux的工作流程是单向数据流:

  1. 动作触发:视图层(如React组件)通过用户交互或者其他事件(例如点击按钮)触发一个action
  2. 动作分发:这个actionstore.dispatch()方法分发到reducer
  3. 状态更新reducer根据传入的action和当前state,计算并返回一个新的state。这个新的state会替换掉旧的state存储在store中。
  4. 通知订阅者store会通知所有订阅了状态变化的监听器(通过store.subscribe()注册的函数)。这些监听器通常会更新视图层,使得UI能够反映最新的状态。

四、场景应用

1. 大型复杂的单页应用(SPA)

  • 在大型SPA中,应用状态可能会非常复杂,涉及多个模块和组件之间的交互。例如,一个电商单页应用,有商品列表展示、购物车管理、用户登录状态等多个功能模块。
  • 商品列表:当用户进行搜索操作时,会触发一个SEARCH_PRODUCTS类型的actionreducer会根据这个action更新商品列表的状态。
  • 购物车管理:添加商品到购物车会触发ADD_TO_CART类型的action,从购物车移除商品会触发REMOVE_FROM_CART等动作,这些动作通过reducer来更新购物车状态。Redux可以很好地管理这些复杂的状态变化,确保每个模块的状态更新是可预测的。

2. 多用户协作的应用

  • 考虑一个实时文档编辑应用,多个用户可以同时编辑一个文档。
  • 当一个用户输入内容时,会触发UPDATE_DOCUMENT类型的action。Redux可以协调各个用户的操作,通过服务器同步数据,使得每个用户看到的文档状态都是最新的。reducer会根据这些操作更新文档的内容状态,并且通过store的订阅机制,及时更新每个用户的视图。

3. 数据持久化和缓存管理

  • 对于一些需要离线支持或者频繁请求相同数据的应用。例如,一个新闻阅读应用,它可以使用Redux来管理已缓存的新闻文章。
  • 当应用首次加载新闻文章时,会触发FETCH_ARTICLES类型的actionreducer更新状态来存储获取到的文章列表。如果应用离线,它可以从Redux存储的状态中读取已缓存的文章,提供给用户阅读。同时,当用户手动刷新文章列表或者设置了定时更新时,又会触发新的FETCH_ARTICLES动作来更新缓存。

五、实例

1. 简单的计数器应用

  • 创建Action
// 定义action类型
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 创建action创建函数
const increment = () => {
    return {
        type: INCREMENT
    };
}
const decrement = () => {
    return {
        type: DECREMENT
    };
}
  • 创建Reducer
const initialState = {
    count: 0
};
const counterReducer = (state = initialState, action) => {
    switch (action.type) {
        case INCREMENT:
            return {
               ...state,
                count: state.count + 1
            };
        case DECREMENT:
            return {
               ...state,
                count: state.count - 1
            };
        default:
            return state;
    }
}
  • 创建Store并使用
import { createStore } from 'redux';
const store = createStore(counterReducer);
// 订阅状态变化并更新UI(这里简单打印)
const unsubscribe = store.subscribe(() => {
    console.log(store.getState().count);
});
// 分发动作
store.dispatch(increment()); 
store.dispatch(increment()); 
store.dispatch(decrement()); 
// 取消订阅
unsubscribe();

2. 待办事项列表应用

  • 创建Action
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
const addTodo = (text) => {
    return {
        type: ADD_TODO,
        payload: text
    };
}
const toggleTodo = (index) => {
    return {
        type: TOGGLE_TODO,
        payload: index
    };
}
  • 创建Reducer
const initialState = {
    todos: []
};
const todoReducer = (state = initialState, action) => {
    switch (action.type) {
     case ADD_TODO:
          return {
             ...state,
              todos: [...state.todos, {text: action.payload, completed: false}]
          };
      case TOGGLE_TODO:
          const newTodos = [...state.todos];
          newTodos[action.payload].completed =!newTodos[action.payload].completed;
          return {
             ...state,
              todos: newTodos
          };
      default:
          return state;
    }
}
  • 创建Store并使用
const store = createStore(todoReducer);
// 订阅状态变化并更新UI(这里简单打印)
const unsubscribe = store.subscribe(() => {
    console.log(store.getState().todos);
});
// 分发动作
store.dispatch(addTodo("Buy milk"));
store.dispatch(addTodo("Read a book"));
store.dispatch(toggleTodo(0));
unsubscribe();

Redux 之 React

在React中使用Redux

一、安装Redux相关库

  1. 首先需要安装reduxreact-redux库。
    • 如果使用npm,可以在项目目录下的终端中运行以下命令:
    npm install redux react-redux
    
    • 如果使用yarn,则运行:
    yarn add redux react-redux
    

二、创建Redux Store和相关逻辑

  1. 定义Actions(动作)
    • 在React项目中,通常在一个单独的文件(如actions.js)中定义动作。例如,对于一个简单的计数器应用,定义增加和减少计数的动作:
    // actions.js
    export const INCREMENT = 'INCREMENT';
    export const DECREMENT = 'DECREMENT';
    export const increment = () => {
        return {
            type: INCREMENT
        };
    };
    export const decrement = () => {
        return {
            type: DECREMENT
        };
    };
    
  2. 创建Reducers(纯函数)
    • 同样在一个单独的文件(如reducers.js)中创建。以计数器应用为例,创建一个简单的reducer来处理计数状态的更新:
    // reducers.js
    import { INCREMENT, DECREMENT } from './actions';
    const initialState = {
        count: 0
    };
    const counterReducer = (state = initialState, action) => {
        switch (action.type) {
            case INCREMENT:
                return {
                   ...state,
                    count: state.count + 1
                };
            case DECREMENT:
                return {
                   ...state,
                    count: state.count - 1
                };
            default:
                return state;
        }
    };
    export default counterReducer;
    
  3. 创建Store(仓库)
    • 在项目的入口文件(如index.js)或者专门的store.js文件中创建store。例如:
    // store.js
    import { createStore } from 'redux';
    import counterReducer from './reducers';
    const store = createStore(counterReducer);
    export default store;
    

三、在React组件中使用Redux

  1. 使用Provider组件包裹根组件
    • index.js文件中,将整个React应用包裹在Provider组件内,使得应用中的所有组件都可以访问Redux store。Providerreact - redux库提供的组件。
    import React from 'react';
    import ReactDOM from 'react - dom';
    import App from './App';
    import { Provider } from 'react - redux';
    import store from './store';
    ReactDOM.render(
        <Provider store={store}>
            <App />
        </Provider>,
        document.getElementById('root')
    );
    
  2. 在组件中连接Redux(使用connect函数)
    • 功能性组件(使用useSelectoruseDispatch
      • 对于功能性组件,可以使用react - redux提供的useSelectoruseDispatch钩子函数来获取状态和分发动作。例如,在一个计数器组件中:
      import React from 'react';
      import { useSelector, useDispatch } from 'react - redux';
      import { increment, decrement } from './actions';
      const Counter = () => {
          const count = useSelector((state) => state.count);
          const dispatch = useDispatch();
          return (
              <div>
                <p>Count: {count}</p>
                 <button onClick={() => dispatch(increment())}>Increment</button>
                 <button onClick={() => dispatch(decrement())}>Decrement</button>
              </div>
          );
      };
      export default Counter;
      
    • 类组件(使用connect函数)
      • 对于类组件,使用connect函数来连接Redux。例如,将上面的计数器组件改写为类组件:
      import React, { Component } from 'react';
      import { connect } from 'react - redux';
      import { increment, decrement } from './actions';
      class Counter extends Component {
          render() {
              const { count, increment, decrement } = this.props;
              return (
                  <div>
                      <p>Count: {count}</p>
                      <button onClick={increment}>Increment</button>
                      <button onClick={decrement}>Decrement</button>
                  </div>
              );
          }
      }
      const mapStateToProps = (state) => ({
          count: state.count
      });
      const mapDispatchToProps = {
          increment,
          decrement
      };
      export default connect(mapStateToProps, mapDispatchToProps)(Counter);
      
    • mapStateToProps函数
      • 这个函数用于将Redux store中的状态映射到组件的props中。它接收整个Redux状态树作为参数,返回一个对象,对象中的属性会被合并到组件的props中。例如,在上面的类组件中,mapStateToPropsstate.count映射为组件的count属性。
    • mapDispatchToProps函数(或对象)
      • 这个函数(或对象)用于将动作创建函数映射到组件的props中,使得组件可以通过props来分发动作。在类组件的例子中,mapDispatchToProps是一个对象,它将incrementdecrement动作创建函数直接作为属性,这些属性会被添加到组件的props中,从而可以在组件中通过this.props.incrementthis.props.decrement来分发动作。在功能性组件中,useDispatch钩子函数返回的dispatch函数用于手动分发动作,更加灵活。

类组件connect具体应用

导出复用原理

  • connect函数的作用机制
    • 在React - Redux中,connect函数是一个高阶函数,用于将React组件与Redux的store连接起来。它接收两个参数:mapStateToPropsmapDispatchToProps(这两个参数也可以是函数或者对象,用于不同的映射方式)。
    • mapStateToProps原理:这个函数用于将Redux store中的状态映射到组件的props中。它会在store状态发生变化时被调用,每次调用都会重新计算返回的对象。返回对象中的属性会作为props传递给组件。例如,如果mapStateToProps返回{ count: state.count },那么组件就会通过props.count来访问Redux中的count状态。
    • mapDispatchToProps原理:这个函数(或对象)用于将Redux中的action创建函数映射到组件的props中。当它是一个函数时,接收dispatch作为参数,返回一个对象,对象中的属性是action创建函数,这些函数内部会调用dispatch来分发action。当它是一个对象时,对象中的属性直接就是action创建函数,connect函数会自动将它们绑定到dispatch上,这样组件就可以通过props来调用这些action创建函数。
  • 组件复用原理
    • 当一个通过connect函数连接了Redux的组件被导出后,它可以在多个地方被复用。因为mapStateToPropsmapDispatchToProps的映射关系是基于组件的使用场景和Redux的store结构来定义的,所以只要Redux store的结构和组件所需的状态及action保持一致,组件就可以在不同的地方复用。例如,一个显示用户信息的组件,它通过mapStateToProps获取用户状态,通过mapDispatchToProps获取更新用户信息的action函数,只要在其他地方也有相同的Redux store结构和对用户信息的操作需求,这个组件就可以直接复用。

实例

1. 安装依赖

确保项目中安装了 reduxreact-redux

npm install redux react-redux

2. 创建Redux相关文件

actions.js

定义购物车相关的 action 类型和 action 创建函数。

// actions.js
export const ADD_ITEM = 'ADD_ITEM';
export const REMOVE_ITEM = 'REMOVE_ITEM';
export const INCREMENT_QUANTITY = 'INCREMENT_QUANTITY';
export const DECREMENT_QUANTITY = 'DECREMENT_QUANTITY';

export const addItem = (product) => ({
    type: ADD_ITEM,
    payload: product
});

export const removeItem = (productId) => ({
    type: REMOVE_ITEM,
    payload: productId
});

export const incrementQuantity = (productId) => ({
    type: INCREMENT_QUANTITY,
    payload: productId
});

export const decrementQuantity = (productId) => ({
    type: DECREMENT_QUANTITY,
    payload: productId
});
reducers.js

创建购物车的 reducer,处理各种 action 对购物车状态的更新。

// reducers.js
import { ADD_ITEM, REMOVE_ITEM, INCREMENT_QUANTITY, DECREMENT_QUANTITY } from './actions';

const initialState = {
    cart: []
};

const cartReducer = (state = initialState, action) => {
    switch (action.type) {
        case ADD_ITEM:
            return {
              ...state,
                cart: [...state.cart, {
                  ...action.payload,
                    quantity: 1
                }]
            };
        case REMOVE_ITEM:
            return {
              ...state,
                cart: state.cart.filter(item => item.id!== action.payload)
            };
        case INCREMENT_QUANTITY:
            return {
              ...state,
                cart: state.cart.map(item =>
                    item.id === action.payload? {
                      ...item,
                        quantity: item.quantity + 1
                    } : item
                )
            };
        case DECREMENT_QUANTITY:
            return {
              ...state,
                cart: state.cart.map(item =>
                    item.id === action.payload && item.quantity > 1? {
                      ...item,
                        quantity: item.quantity - 1
                    } : item
                )
            };
        default:
            return state;
    }
};

export default cartReducer;
store.js

创建Redux的 store

// store.js
import { createStore } from'redux';
import cartReducer from './reducers';

const store = createStore(cartReducer);

export default store;

3. 创建React类组件

Product.js

定义商品展示组件,该组件可以触发添加商品到购物车的操作。

// Product.js
import React, { Component } from'react';
import { connect } from'react - redux';
import { addItem } from './actions';

class Product extends Component {
    render() {
        const { product, addItem } = this.props;
        return (
            <div>
                <h3>{product.name}</h3>
                <p>{product.price}</p>
                <button onClick={() => addItem(product)}>Add to Cart</button>
            </div>
        );
    }
}

const mapDispatchToProps = {
    addItem
};

export default connect(null, mapDispatchToProps)(Product);
CartItem.js

定义购物车中商品项的展示组件,该组件可以触发移除商品、增加和减少商品数量的操作。

// CartItem.js
import React, { Component } from'react';
import { connect } from'react - redux';
import { removeItem, incrementQuantity, decrementQuantity } from './actions';

class CartItem extends Component {
    render() {
        const { item, removeItem, incrementQuantity, decrementQuantity } = this.props;
        return (
            <div>
                <p>{item.name}</p>
                <p>Quantity: {item.quantity}</p>
                <button onClick={() => incrementQuantity(item.id)}>Increment</button>
                <button onClick={() => decrementQuantity(item.id)}>Decrement</button>
                <button onClick={() => removeItem(item.id)}>Remove</button>
            </div>
        );
    }
}

const mapDispatchToProps = {
    removeItem,
    incrementQuantity,
    decrementQuantity
};

export default connect(null, mapDispatchToProps)(CartItem);
Cart.js

定义购物车展示组件,复用 CartItem 组件展示购物车中的所有商品。

// Cart.js
import React, { Component } from'react';
import { connect } from'react - redux';
import CartItem from './CartItem';

class Cart extends Component {
    render() {
        const { cart } = this.props;
        return (
            <div>
                <h2>Shopping Cart</h2>
                {cart.map(item => (
                    <CartItem key={item.id} item={item} />
                ))}
            </div>
        );
    }
}

const mapStateToProps = (state) => ({
    cart: state.cart
});

export default connect(mapStateToProps)(Cart);
App.js

App 组件中复用 ProductCart 组件,展示商品列表和购物车。

// App.js
import React, { Component } from'react';
import { Provider } from'react - redux';
import store from './store';
import Product from './Product';
import Cart from './Cart';

const products = [
    { id: 1, name: 'Product 1', price: 10 },
    { id: 2, name: 'Product 2', price: 15 }
];

class App extends Component {
    render() {
        return (
            <Provider store = {store}>
                <div>
                    <h1>Redux Shopping Cart Example</h1>
                    <div>
                        <h2>Products</h2>
                        {products.map(product => (
                            <Product key={product.id} product={product} />
                        ))}
                    </div>
                    <Cart />
                </div>
            </Provider>
        );
    }
}

export default App;

4. 总结

在上述代码中,Product 组件和 CartItem 组件都通过 connect 函数与Redux建立连接,能够触发Redux中的方法(action)。Cart 组件复用了 CartItem 组件来展示购物车中的商品。整个应用通过 Provider 组件将Redux的 store 提供给所有子组件,实现了状态管理和组件间的交互。

redux 中间件应用及介绍

  1. Redux-Thunk
    • 作用
      • 用于处理异步操作。它允许action创建函数返回一个函数,而不是一个普通的action对象。这个返回的函数可以接收dispatchgetState作为参数,从而在函数内部执行异步操作(如网络请求),并根据异步操作的结果来分发action
    • 实例
      • 假设我们要从一个API获取用户数据并更新Redux状态。
      • 首先,定义action类型和action创建函数:
        // actions.js
        const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
        const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
        const fetchUser = () => {
            return async (dispatch) => {
                try {
                  const response = await fetch('https://example.com/api/user');
                   const userData = await response.json();
                   dispatch({
                       type: FETCH_USER_SUCCESS,
                       payload: userData
                   });
                } catch (error) {
                    dispatch({
                        type: FETCH_USER_FAILURE,
                        payload: error.message
                    });
                }
            };
        };
        
      • 然后,创建reducer来处理action
        // reducers.js
        const initialState = {
            user: null,
            error: null
        };
        const userReducer = (state = initialState, action) => {
            switch (action.type) {
                case FETCH_USER_SUCCESS:
                    return {
                       ...state,
                        user: action.payload
                    };
                case FETCH_USER_FAILURE:
                    return {
                       ...state,
                        error: action.payload
                    };
                default:
                    return state;
            }
        };
        export default userReducer;
        
      • 最后,在创建store时应用redux - thunk中间件:
        // store.js
        import { createStore, applyMiddleware } from 'redux';
        import thunkMiddleware from 'redux - thunk';
        import userReducer from './reducers';
        const store = createStore(
            userReducer,
            applyMiddleware(thunkMiddleware)
        );
        
  2. Redux-Saga
    • 作用
      • 也是用于处理异步操作,它基于Generator函数。Redux - Saga将异步操作看作是可以被暂停和恢复的一系列步骤,通过Generator函数的yield关键字来暂停Saga的执行,等待异步操作完成后再恢复执行。这样可以更方便地处理复杂的异步流程,如并发请求、轮询等。
    • 实例
      • 同样是获取用户数据的例子。
      • 首先,定义Saga函数:
        // sagas.js
        import { call, put, takeEvery } from 'redux - saga/effects';
        const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
        const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
        function* fetchUserSaga() {
            try {
            const response = yield call(fetch, 'https://example.com/api/user');
              const userData = yield call([response, 'json']);
              yield put({
                  type: FETCH_USER_SUCCESS,
                  payload: userData
              });
            } catch (error) {
                yield put({
                    type: FETCH_USER_FAILURE,
                    payload: error.message
                });
            }
        }
        function* rootSaga() {
            yield takeEvery('FETCH_USER', fetchUserSaga);
        }
        export default rootSaga;
        
      • 然后,创建reducer(和上面Redux - Thunk例子中的reducer类似):
        // reducers.js
        const initialState = {
            user: null,
            error: null
        };
        const userReducer = (state = initialState, action) => {
            switch (action.type) {
                case FETCH_USER_SUCCESS:
                    return {
                       ...state,
                        user: action.payload
                    };
                case FETCH_USER_FAILURE:
                    return {
                       ...state,
                        error: action.payload
                    };
                default:
                    return state;
            }
        };
        export default userReducer;
        
      • 最后,在创建store时应用Redux - Saga中间件:
        // store.js
        import { createStore, applyMiddleware } from 'redux';
        import createSagaMiddleware from 'redux - saga';
        import userReducer from './reducers';
        import rootSaga from './sagas';
        const sagaMiddleware = createSagaMiddleware();
        const store = createStore(
            userReducer,
            applyMiddleware(sagaMiddleware)
        );
        sagaMiddleware.run(rootSaga);
        
  3. Redux - Logger
    • 作用
      • 用于日志记录,它可以在action被分发、reducer处理action以及状态更新的过程中记录相关信息。这对于调试Redux应用非常有帮助,可以清楚地看到action的流向和状态的变化情况。
    • 实例
      • 定义logger中间件:
        // loggerMiddleware.js
        const loggerMiddleware = (store) => (next) => (action) => {
            console.log('dispatching', action);
            const result = next(action);
            console.log('next state', store.getState());
            return result;
        };
        export default loggerMiddleware;
        
      • 在创建store时应用logger中间件:
        // store.js
        import { createStore, applyMiddleware } from 'redux';
        import loggerMiddleware from './loggerMiddleware';
        import rootReducer from './reducers';
        const store = createStore(
            rootReducer,
            applyMiddleware(loggerMiddleware)
        );
        
      • action被分发时,例如在一个计数器应用中:
        // actions.js
        const INCREMENT = 'INCREMENT';
        const increment = () => ({
            type: INCREMENT
        });
        
        // counterReducer.js
        const initialState = {
            count: 0
        };
        const counterReducer = (state = initialState, action) => {
            switch (action.type) {
                case INCREMENT:
                    return {
                       ...state,
                        count: state.count + 1
                    };
                default:
                    return state;
            }
        };
        export default counterReducer;
        
        // index.js
        import React from 'react';
        import ReactDOM from 'react - dom';
        import { Provider } from 'react - redux';
        import store from './store';
        import Counter from './Counter';
        ReactDOM.render(
            <Provider store={store}>
                <Counter />
            </Provider>,
            document.getElementById('root')
        );
        
        • 当点击按钮触发increment动作时,logger中间件会在控制台打印出分发的action{type: 'INCREMENT'})以及更新后的状态({count: 1}等)。

原文地址:https://blog.csdn.net/m0_51244077/article/details/144788573

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!