在没有使用 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
。