redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。
如果你的应用有以下场景,可以考虑使用 Redux。
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
redux 的基本概念
Store
Store 就是保存数据的地方。整个应用只能有一个 Store。
Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
Redux 提供createStore这个函数,用来生成 Store。
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
Action
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。
Action 本质上是 JavaScript 普通对象。其中的type
属性是必须的,表示 Action 的名称。
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
Action 创建函数
Action 创建函数 就是生成 action 的方法。
const ADD_TODO = "ADD_TODO"
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
Reducer
Reducer 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
const reducer = function (state, action) {
// ...
return new_state;
};
整个应用的初始状态,可以作为 State 的默认值。
const defaultState = 0;
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
};
const state = reducer(0, {
type: 'ADD',
payload: 2
});
实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch方法会触发 Reducer 的自动执行。
import { createStore } from 'redux';
const store = createStore(reducer);
store.dispatch({
type: 'ADD',
payload: 2
});
Reducer 函数最重要的特征是,它是一个纯函数。
纯函数是函数式编程的概念,必须遵守以下一些约束。
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果
Reducer 函数里面不能改变 State,必须返回一个全新的对象。
// State 是一个对象
function reducer(state, action) {
return Object.assign({}, state, { newState });
// 或者
return { ...state, ...newState };
}
// State 是一个数组
function reducer(state, action) {
return [...state, newItem];
}
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
store.subscribe方法返回一个函数,调用这个函数就可以解除监听。
import { createStore } from 'redux';
const store = createStore(reducer);
let unsubscribe = store.subscribe(()=>{
console.log(store.getState())
});
unsubscribe()
Reducer 的拆分
Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp;
等价于:
export default function todoApp(state = {}, action) {
return {
visibilityFilter: visibilityFilter(state.visibilityFilter, action),
todos: todos(state.todos, action)
}
}
简单实例
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import { createStore } from "redux"
import reducer from "./reducers/counter"
const store = createStore(reducer)
const render = () => {
ReactDOM.render(
<App
onIncrement={() => store.dispatch({ type: "INCREMENT" })}
onDecrement={() => store.dispatch({ type: "DECREMENT" })}
value={store.getState()}
/>,
document.getElementById("root")
)
}
render()
store.subscribe(render)
app.js
import React from "react"
class App extends React.Component {
render() {
return (
<div className='App'>
<p>{this.props.value}</p>
<button onClick={this.props.onIncrement}>+1</button>
<button onClick={this.props.onDecrement}>-1</button>
</div>
)
}
}
export default App
counter.js
const counter = (state = 0, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1
case "DECREMENT":
return state - 1
default:
return state
}
}
export default counter
三大原则
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers。
中间件
使用中间件
import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';
const store = createStore(
reducer,
{},
applyMiddleware(logger)
);
redux-thunk 中间件
redux-thunk 中间件处理异步操作,比如setTimout、网络请求等等。
// index
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducers";
const store = createStore(reducer, {}, applyMiddleware(thunk))
// action
export function increment(num) {
return (dispatch) => {
setTimeout(() => {
dispatch({
type: actions.INCREMENT,
num,
})
}, 1000)
}
}
react-redux
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 没有状态(即不使用this.state这个变量)
- 所有数据都由参数(this.props)提供
- 不使用任何 Redux 的 API
容器组件的特征:
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。
import { connect } from 'react-redux'
const VisibleTodoList = connect(mapStateToProps,mapDispatchToProps)(TodoList);
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
mapStateToProps()
mapStateToProps是一个函数。mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。
const mapStateToProps = (state) => {
return {
todos: state.todos
}
}
mapDispatchToProps()
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。
const mapDispatchToProps = (
dispatch
) => {
return {
onClick: () => {
dispatch({
type: 'ADD_TODO'
});
}
};
}
bindActionCreators 写法
import * as counterActions from "./actions/counter"
import { bindActionCreators } from "redux"
const mapDispatchToProps = (dispatch) => {
return {
counterActions: bindActionCreators(counterActions, dispatch),
}
}
actions/counter.js
export function increment(num) {
return {
type: "INCREMENT",
num,
}
}
export function decrement(num) {
return {
type: "DECREMENT",
num,
}
}
Provider 组件
connect 方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
简单实例
import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import { createStore } from "redux"
import reducer from "./reducers/counter"
import { Provider } from "react-redux"
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
)
App.js
import React from "react"
import { connect } from "react-redux"
import { increment, decrement } from "./actions/counter"
class App extends React.Component {
render() {
const { count, increment, decrement } = this.props
return (
<div className='App'>
<p>{count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
count: state.count,
}
}
const mapDispatchToProps = (dispatch) => {
return {
increment: () => {
dispatch(increment())
},
decrement: () => {
dispatch(decrement())
},
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
actions/counter.js
export function increment() {
return {
type: "INCREMENT",
}
}
export function decrement() {
return {
type: "DECREMENT",
}
}
reducers/counter.js
const counter = (state = { count: 0 }, action) => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 }
case "DECREMENT":
return { count: state.count - 1 }
default:
return state
}
}
export default counter
redux 调试工具
1.chrome 浏览器安装插件:Redux DevTools
2.安装依赖
npm install --save-dev redux-devtools-extension
3.使用
import { composeWithDevTools } from "redux-devtools-extension"
const store = createStore(
rootReducer,
{},
composeWithDevTools(applyMiddleware(logger, thunk))
)
网友评论