ReactJS - State management



我们将执行以下操作来管理我们的 redux store。

  • 通过 async fetch api 从服务器获取费用,并在 Redux store 中设置。
  • 通过异步 fetch 编程向服务器添加新费用,并在 Redux store 中设置添加新费用。
  • 通过 async fetch api 从服务器中删除现有费用并更新 Redux store。

让我们创建 action types、action creator、actions 和 reducer 来管理 Redux state。

在 src folder 下创建文件夹 actions。

接下来,创建一个文件,types.js创建操作类型。


export const LIST_EXPENSE_STARTED = 'LIST_EXPENSE_STARTED';
export const LIST_EXPENSE_SUCCESS = 'LIST_EXPENSE_SUCCESS';
export const LIST_EXPENSE_FAILURE = 'LIST_EXPENSE_FAILURE';

export const ADD_EXPENSE_STARTED = 'ADD_EXPENSE_STARTED';
export const ADD_EXPENSE_SUCCESS = 'ADD_EXPENSE_SUCCESS';
export const ADD_EXPENSE_FAILURE = 'ADD_EXPENSE_FAILURE';

export const DELETE_EXPENSE_STARTED = 'DELETE_EXPENSE_STARTED';
export const DELETE_EXPENSE_SUCCESS = 'DELETE_EXPENSE_SUCCESS';
export const DELETE_EXPENSE_FAILURE = 'DELETE_EXPENSE_FAILURE';

接下来,创建一个文件,index.js actions folder 下创建操作创建者。


import {
	 	LIST_EXPENSE_STARTED, LIST_EXPENSE_SUCCESS, LIST_EXPENSE_FAILURE,
	 	ADD_EXPENSE_STARTED, ADD_EXPENSE_SUCCESS, ADD_EXPENSE_FAILURE,
	 	DELETE_EXPENSE_STARTED, DELETE_EXPENSE_SUCCESS, DELETE_EXPENSE_FAILURE,
} from "./types";
export const getExpenseListStarted = () => {
	 	return {
	 	 	 type: LIST_EXPENSE_STARTED
	 	}
}
export const getExpenseListSuccess = data => {
	 	return {
	 	 	 type: LIST_EXPENSE_SUCCESS,
	 	 	 payload: {
	 	 	 	 	data
	 	 	 }
	 	}
}
export const getExpenseListFailure = error => {
	 	return {
	 	 	 type: LIST_EXPENSE_FAILURE,
	 	 	 payload: {
	 	 	 	 	error
	 	 	 }
	 	}
}
export const addExpenseStarted = () => {
	 	return {
	 	 	 type: ADD_EXPENSE_STARTED
	 	}
}
export const addExpenseSuccess = data => {
	 	return {
	 	 	 type: ADD_EXPENSE_SUCCESS,
	 	 	 payload: {
	 	 	 	 	data
	 	 	 }
	 	}
}
export const addExpenseFailure = error => {
	 	return {
	 	 	 type: ADD_EXPENSE_FAILURE,
	 	 	 payload: {
	 	 	 	 	error
	 	 	 }
	 	}
}
export const deleteExpenseStarted = () => {
	 	return {
	 	 	 type: DELETE_EXPENSE_STARTED
	 	}
}
export const deleteExpenseSuccess = data => {
	 	return {
	 	 	 type: DELETE_EXPENSE_SUCCESS,
	 	 	 payload: {
	 	 	 	 	data
	 	 	 }
	 	}
}
export const deleteExpenseFailure = error => {
	 	return {
	 	 	 type: DELETE_EXPENSE_FAILURE,
	 	 	 payload: {
	 	 	 	 	error
	 	 	 }
	 	}
}

在这里,我们为 fetch api 的每个可能结果(成功、失败和错误)创建了一个操作创建器。由于我们将使用 3 个 Web API 调用,并且每个调用将有三种可能的结果,因此我们使用 9 个 action creator。

接下来,创建一个文件,expenseActions.js actions folder 下,并创建三个函数来获取、添加和删除费用以及分派状态更改。


import {
	 	getExpenseListStarted, getExpenseListSuccess, getExpenseListFailure,
	 	addExpenseStarted, addExpenseSuccess, addExpenseFailure,
	 	deleteExpenseStarted, deleteExpenseSuccess, deleteExpenseFailure
} from "./index";
export const getExpenseList = () => async dispatch => {
	 	dispatch(getExpenseListStarted());
	 	try {
	 	 	 const res = await fetch('http://localhost:8000/api/expenses');
	 	 	 const data = await res.json();
	 	 	 var items = [];
	 	 	 data.forEach((item) => {
	 	 	 	 	let newItem = {
	 	 	 	 	 	 id: item._id,
	 	 	 	 	 	 name: item.name,
	 	 	 	 	 	 amount: item.amount,
	 	 	 	 	 	 spendDate: item.spend_date,
	 	 	 	 	 	 category: item.category
	 	 	 	 	}
	 	 	 	 	items.push(newItem)
	 	 	 });
	 	 	 dispatch(getExpenseListSuccess(items));
	 	} catch (err) {
	 	 	 dispatch(getExpenseListFailure(err.message));
	 	}
}
export const addExpense = (data) => async dispatch => {
	 	dispatch(addExpenseStarted());

	 	let newItem = {
	 	 	 name: data.name,
	 	 	 amount: data.amount,
	 	 	 spend_date: data.spendDate,
	 	 	 category: data.category
	 	}
	 	console.log(newItem);
	 	try {
	 	 	 const res = await fetch('http://localhost:8000/api/expense', {
	 	 	 	 	method: 'POST',
	 	 	 	 	body: JSON.stringify(newItem),
	 	 	 	 	headers: {
	 	 	 	 	 	 "Content-type": "application/json; charset=UTF-8"
	 	 	 	 	}	
	 	 	 });
	 	 	 const data = await res.json();
	 	 	 newItem.id = data._id;
	 	 	 dispatch(addExpenseSuccess(newItem));
	 	} catch (err) {
	 	 	 console.log(err);
	 	 	 dispatch(addExpenseFailure(err.message));
	 	}
}
export const deleteExpense = (id) => async dispatch => {
	 	dispatch(deleteExpenseStarted());
	 	try {
	 	 	 const res = await fetch('http://localhost:8000/api/expense/' + id, {
	 	 	 	 	method: 'DELETE'
	 	 	 });
	 	 	 const data = await res.json();
	 	 	 dispatch(deleteExpenseSuccess(id));
	 	} catch (err) {
	 	 	 dispatch(deleteExpenseFailure(err.message));
	 	}
}

这里

  • 使用异步获取 api 进行 Web API 调用。
  • 使用 dispatch 函数在成功、失败和错误事件期间调度适当的 action。

创建一个文件夹,在 src 文件夹下创建一个 reducers 并创建一个文件,index.js reducers 文件夹下创建 Redux Reducers。


import {
	 	LIST_EXPENSE_STARTED, LIST_EXPENSE_SUCCESS, LIST_EXPENSE_FAILURE,
	 	ADD_EXPENSE_STARTED, ADD_EXPENSE_SUCCESS, ADD_EXPENSE_FAILURE,
	 	DELETE_EXPENSE_STARTED, DELETE_EXPENSE_SUCCESS, DELETE_EXPENSE_FAILURE
} from "../actions/types";

// define initial state of user
const initialState = {
	 	data: null,
	 	loading: false,
	 	error: null
}
export default function expenseReducer(state = initialState, action) {
	 	switch (action.type) {
	 	 	 case LIST_EXPENSE_STARTED:
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 loading: true
	 	 	 	 	}
	 	 	 case LIST_EXPENSE_SUCCESS:
	 	 	 	 	const { data } = action.payload;
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 data,
	 	 	 	 	 	 loading: false
	 	 	 	 	}
	 	 	 case LIST_EXPENSE_FAILURE:
	 	 	 	 	const { error } = action.payload;
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 error
	 	 	 	 	}
	 	 	 case ADD_EXPENSE_STARTED:
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 loading: true
	 	 	 	 	}
	 	 	 case ADD_EXPENSE_SUCCESS:
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 loading: false
	 	 	 	 	}
	 	 	 case ADD_EXPENSE_FAILURE:
	 	 	 	 	const { expenseError } = action.payload;
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 expenseError
	 	 	 	 	}
	 	 	 case DELETE_EXPENSE_STARTED:
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 loading: true
	 	 	 	 	}
	 	 	 case DELETE_EXPENSE_SUCCESS:
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 data: state.data.filter(expense => expense.id !== action.payload.data),
	 	 	 	 	 	 loading: false
	 	 	 	 	}
	 	 	 case DELETE_EXPENSE_FAILURE:
	 	 	 	 	const { deleteError } = action.payload;
	 	 	 	 	return {
	 	 	 	 	 	 ...state,
	 	 	 	 	 	 deleteError
	 	 	 	 	}
	 	 	 default:
	 	 	 	 	return state
	 	}
}

在这里,我们更新了每种 action 类型的 redux store state。

接下来,在 src 文件夹下打开index.js文件并包含 Provider 组件,以便所有组件都可以连接并使用 redux store。


import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import rootReducer from './reducers';
import App from './components/App';

const store = createStore(rootReducer, applyMiddleware(thunk));

ReactDOM.render(
	 	<Provider store={store}>
	 	 	 <App />
	 	</Provider>,
	 	document.getElementById('root')
);

这里

  • 导入 createStore 和 applyMiddleware
  • 从 redux-thunk 库导入 thunk(用于异步获取 api)
  • 从 redux 库导入的 Provider
  • 通过配置 reducer 和 thunk middleware 使用 createStore 创建 newstore
  • 将 Provider 组件作为顶级组件附加到 redux store