在沒有使用 Redux
的 React
項目中的組件通訊和狀態管理是特別繁瑣的,好比子組件和父組件通訊改變值,要經過父組件的方法。使用 Redux
能夠解決組件之間的通訊問題,由於 React
提出將展現組件與容器組件分離的思想,因此下降了 React
與 Redux
之間的耦合度。不少人都說,簡單的應用能夠不用此工具。可是我我的認爲,中小型應用使用的話,可使文件結構更加規範,代碼可讀性更強。javascript
npm i redux -S
java
state
單一數據源原則:整個應用的 state
數據都被儲存在一棵 object tree
中,而且這個 object tree
只存在於惟一一個 store
中。node
state
其實就是一個普通的 JSON
對象。該對象包含 redux
的全部數據。react
當前的 state
,能夠經過 store.getState()
獲取。npm
Redux
規定, 一個 State
對應一個 View
。只要 State
相同,View
就相同。你知道 State
,就知道 View
是什麼樣,反之亦然。redux
建議定義 state tree
層級結構不要太深。bash
{
text: 'text 文本',
todos: [1,2,3,4]
}
複製代碼
action
State
只讀原則:惟一改變 state
的方法就是觸發 action
,action
是一個用於描述已發生事件的普通對象。dom
咱們約定:action
內必須使用一個字符串類型的 type
字段來表示將要執行的動做。一般其餘參數用 payload
字段來存儲以便管理方便,結構清晰。異步
const ADD_TODO = 'ADD_TODO'
// action
{
type: ADD_TODO,
payload: {
text: 'Build my first action'
}
}
複製代碼
Action
建立函數Action
建立函數其實就是生成 action
的方法。「action」 和 「action 建立函數」 這兩個概念很容易混在一塊兒,使用時最好注意區分。ide
有時候改變多少消息是,就會有多少個 action
。若是每次都寫一遍相似的操做,就會顯得繁瑣、麻煩。咱們能夠經過定義一個函數來生成一個 Action
,這個函數就叫 Action
建立函數。
const updateText = 'updateText'
// action 建立函數
function addTodo(text) {
return {
type: updateText,
payload: {
text,
}
}
}
const action = addTodo('Action Creator');
複製代碼
Reducer
使用純函數修改state
原則:爲了描述 action
如何改變 state tree
,你須要編寫 reducers
。
Reducer
是一個純函數,只能經過使用Reducer
用來修改 state tree
的數據。
Reducer
是接收 state
和 action
參數,返回新的 state
的一個函數。
Reducer
必須遵照的約束條件:
Date.now()
或 Math.random()
,由於每次會獲得不同的結果,沒法預測 Reducer
執行後的結果。(previousState, action) => newState
複製代碼
const initialState = {
text: 'text 文本',
todos: [1,2,3,4]
};
function updateText(state = initialState, action) {
switch (action.type) {
case 'updateText':
return Object.assign({}, state, {
text: action.payload.text
})
default:
return state
}
}
複製代碼
Store
使用 action
來描述「發生了什麼」,和使用 reducers
來根據 action
更新 state
的用法。Store
就是把它們聯繫到一塊兒的對象。Store
有如下職責:
state
getState()
方法獲取 state
dispatch(action)
方法更新 state
subscribe(listener)
註冊監聽器subscribe(listener)
返回的函數註銷監聽器store.dispatch(action)
觸發 Action
Store
調用傳入的 reducer
函數(此過程傳入當前的 state tress
和 action
)reducer
計算下一個 state
返回給 Store
並更新 state tree
Store
根據新的 state tree
從新渲染 View
。React
集成 Redux
前面咱們介紹了 redux
的核心概念和工做流程,接着咱們介紹 React
集成 Redux
功能。
首先,安裝 Redux
的 React
綁定庫:
npm i react-redux -S
爲了讓頁面的全部容器組件均可以訪問 Redux store
,因此能夠手動監聽它。
一種方式是把它以 props
的形式傳入到全部容器組件中。但這太麻煩了,由於必需要用 store
把展現組件包裹一層,僅僅是由於剛好在組件樹中渲染了一個容器組件。
還有就是使用 React Redux
組件 <Provider>
來讓全部容器組件均可以訪問 store
,而沒必要顯示地傳遞它。(推薦使用)。
Redux
例子這裏須要再強調一下: Redux
和 React
之間沒有關係。Redux
支持 React、Angular、Ember、jQuery
甚至純 JavaScript
。
儘管如此,Redux
仍是和 React
和 Deku
這類庫搭配起來用最好,由於這類庫容許你以 state
函數的形式來描述界面,Redux
經過 action
的形式來發起 state
變化。
這裏咱們介紹一下單純使用 Redux
來實現其用法:
store
管理reduxDemo/store.ts
複製代碼
import { createStore, Store, Reducer } from 'redux';
interface IState {
num: number;
}
const initState = {
num: 1,
};
const reducers: Reducer<IState> = (state = initState, action) => {
switch (action.type) {
case 'add':
return {
...state,
num: state.num + 1,
};
case 'minus':
return {
...state,
num: state.num - 1,
};
default:
return state;
}
};
const store: Store<IState> = createStore(reducers);
export default store;
複製代碼
reduxDemo/index.tsx
複製代碼
import * as React from 'react';
import { Unsubscribe } from 'redux';
import store from './store';
class Index extends React.PureComponent {
subscribe?: Unsubscribe;
componentDidMount(): void {
this.subscribe = store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount(): void {
if (this.subscribe) {
this.subscribe();
}
}
render(): JSX.Element {
return (
<div>
<div>{store.getState().num}</div>
<button type="button" onClick={() => store.dispatch({ type: 'add' })}>加一</button>
<button type="button" onClick={() => store.dispatch({ type: 'minus' })}>減一</button>
</div>
);
}
}
export default Index;
複製代碼
react-redux
例子經過上面的例子咱們看到單純在 React
項目中使用 Redux
,咱們還須要手動監聽 Store
中 state
的變化和取消監聽,而後在手動進行強制更新從新渲染組件。
接着咱們舉例使用在 React
項目中使用 react-redux
綁定庫例子:
純UI展現組件,沒有任何交互邏輯,並不關心數據來源和如何改變。
components/Todo.tsx
複製代碼
import React from 'react';
interface IProps {
text: string;
completed: boolean;
onClick?: (e: React.MouseEvent) => void;}
export default function Todo({
onClick,
completed,
text,
}: IProps): JSX.Element {
return (
<li
onClick={onClick}
style={{
textDecoration: completed ? 'line-through' : 'none',
}}
>
{text}
</li>
);
}
複製代碼
components/TodoList.tsx
複製代碼
import React from 'react';
import Todo from './Todo';
interface IProps {
todos: any[];
onTodoClick: (id: number) => void;
}
export default function TodoList({
todos,
onTodoClick,
}: IProps): JSX.Element {
return (
<ul>
{todos.map((todo) => (
<Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} />
))}
</ul>
);
}
複製代碼
components/Link.tsx
複製代碼
import React from 'react';
interface IProps {
active: boolean;
children: any;
onClick: () => void;
}
export default function Link({
active,
children,
onClick,
}: IProps): JSX.Element {
if (active) {
return <span>{children}</span>;
}
return (
<a
href="javascript(0);"
onClick={(e) => {
e.preventDefault();
onClick();
}}
>
{children}
</a>
);
}
複製代碼
components/Footer.tsx
複製代碼
import React from 'react';
import FilterLink from '../containers/FilterLink';
const Footer = (): JSX.Element => (
<p>
Show:
{' '}
<FilterLink filter="SHOW_ALL">
All
</FilterLink>
{', '}
<FilterLink filter="SHOW_ACTIVE">
Active
</FilterLink>
{', '}
<FilterLink filter="SHOW_COMPLETED">
Completed
</FilterLink>
</p>
);
export default Footer;
複製代碼
把 展現組件 鏈接到 Redux
。
containers/FilterLink.tsx
複製代碼
import { connect } from 'react-redux';
import { setVisibilityFilter, namespace } from '../actions';
import Link from '../components/Link';
const mapStateToProps = (state: any, ownProps: any): object => ({
active: ownProps.filter === state[namespace].visibilityFilter,
});
const mapDispatchToProps = (dispatch: any, ownProps: any): object => ({
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter));
},
});
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps,
)(Link);
export default FilterLink;
複製代碼
containers/VisibleTodoList.tsx
複製代碼
import { connect } from 'react-redux';
import { toggleTodo, namespace } from '../actions';
import TodoList from '../components/TodoList';
const getVisibleTodos = (todos: any[], filter: string): any[] => {
switch (filter) {
case 'SHOW_COMPLETED':
return todos.filter((t) => t.completed);
case 'SHOW_ACTIVE':
return todos.filter((t) => !t.completed);
case 'SHOW_ALL':
default:
return todos;
}
};
const mapStateToProps = (state: any): { todos: any[] } => ({
todos: getVisibleTodos(state[namespace].todos, state[namespace].visibilityFilter),
});
interface TDispatchProps {
onTodoClick: (id: number) => void;
}
const mapDispatchToProps = (dispatch: any): TDispatchProps => ({
onTodoClick: (id: number) => {
dispatch(toggleTodo(id));
},
});
const VisibleTodoList = connect<
{
todos: any[];
},
TDispatchProps,
any
>(
mapStateToProps,
mapDispatchToProps,
)(TodoList);
export default VisibleTodoList;
複製代碼
containers/AddTodo.tsx
複製代碼
import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../actions';
interface IProps {
onAddClick: (text: string) => void;
}
export const AddTodo = ({ onAddClick }: IProps): JSX.Element => {
let input: any = null;
return (
<div>
<form
onSubmit={(e) => {
e.preventDefault();
if (!input.value.trim()) {
return;
}
onAddClick(input.value);
input.value = '';
}}
>
<input
ref={(node) => {
input = node;
}}
/>
<button type="submit">Add Todo</button>
</form>
</div>
);
};
export default connect(undefined, (dispatch: any) => ({
onAddClick: (text: string) => dispatch(addTodo(text)),
}))(AddTodo);
複製代碼
action
書寫須要的 action
。
let nextTodoId = 2;
export const namespace = 'pages/index';
export const ADD_TODO = 'ADD_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const addTodo = (text: string): object => {
nextTodoId += 1;
return {
type: ADD_TODO,
payload: {
id: nextTodoId,
text,
},
};
};
export const setVisibilityFilter = (filter: string): object => ({
type: SET_VISIBILITY_FILTER,
payload: {
filter,
},
});
export const toggleTodo = (id: number): object => ({
type: TOGGLE_TODO,
payload: {
id,
},
});
複製代碼
Reducer
書寫 Reducer
更新 ```state tree``。
import { ReducerBuildOptions } from '@src/redux/reducer/typings';
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
namespace,
} from './actions';
export interface ModalState {
visibilityFilter: string;
todos: {
id: number;
text?: string;
completed?: boolean;
}[];
}
export const initialState: ModalState = {
visibilityFilter: 'SHOW_ALL',
todos: [
{
id: 1,
text: 'Consider using Redux',
completed: true,
},
{
id: 2,
text: 'Keep all state in a single tree',
completed: false,
},
],
};
export interface ModalReducers {
updateState: ReducerBuildOptions<ModalState, ModalState>;
[TOGGLE_TODO]: ReducerBuildOptions<ModalState, { id: number }>;
[ADD_TODO]: ReducerBuildOptions<ModalState, { id: number; text: string }>;
[SET_VISIBILITY_FILTER]: ReducerBuildOptions<ModalState>;
}
const reducers: ModalReducers = {
updateState: (state, { payload }) => ({ ...state, ...payload }),
[TOGGLE_TODO]: (state, { payload }) => {
const { todos } = state;
return {
...state,
todos: todos.map((todo) => (todo.id === payload?.id
? {
...todo,
completed: !todo.completed,
}
: todo)),
};
},
[ADD_TODO]: (state, { payload }) => ({
...state,
todos: state.todos.concat({
id: payload?.id || Math.random(),
text: payload?.text,
completed: false,
}),
}),
[SET_VISIBILITY_FILTER]: (state, { payload }) => ({
...state,
...payload,
}),
};
const createReducer = (initialState, handlers) =>(state = initialState, action){
const { type } = action;
if (type in handlers) {
return handlers[type](state, action);
}
return state;
}
export default combineReducers({
[namespace]: createReducer(initialState,reducers)
});
複製代碼
React
集成 Redux
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
// 你的 reducer 集合
import reducers from './reducers'
import App from './components/App'
let store = createStore(reducers)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
複製代碼
Redux
高級教程,其中包含異步流數據處理,動態讀取 reducer
和 saga
Redux
。