redux 虽然以re开头,但其实并不是react官方提供的库。这并不妨碍它成为热门的redux解决方案
所有的状态都保存在一个对象中(store),view视图与状态是一一对应的
如果你不知道是否需要用redux,那你就不需要redux
只有遇到 react 实在解决不了的问题,你才需要 redux
用来保存数据,整个应用只能有一个 Store。通过Redux提供的createStore函数来生成Store。
createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。
import { createStore } from 'redux';
const store = createStore(reducer);
Store共提供了三个方法 getState()、dispatch()、subscribe()
let { subscribe, dispatch, getState } = createStore(reducer);
createStore方法还可以接受第二个参数,表示 State 的最初状态。通常是接口的返回。
从Store中取出的某一时刻的数据。可以使用store.getState()来取得
import { createStore } from 'redux';
const store = createStore(reducer);
const state = store.getState()
用户通过View层的操作来改变Store中的数据,Action 就是 View 发出的通知。它会运送数据到 Store。表示State要变化了
const action = {
type: 'test', // 必须
payload: {}
};
创建Store的时候提到过,createStore函数接受另一个函数作为参数,这个参数就是Reducer。它接受 Action 和当前 State 作为参数,返回一个新的 State。表示会对state如何进行修改。
const reducer = (state, action) => {
switch (action.type) {
case 'ADD':
return state + action.payload;
default:
return state;
}
};
const state = reducer(1, {
type: 'ADD',
payload: 2
});
Reducer是一个纯函数,其接受同样的输入,必定得到同样的输出。因此它必须满足:
- 不能修改参数
- 不能使用I/O
- 不能使用获取时间戳Date.now()或者随机数Math.random()等方法。
实际开发中可以将 reducer
拆分为多个子 ‘reducer’ 在传递给store
之前通过combineReducers()
方法合并成一个reducer
store.dispatch()是 View 发出 Action 的唯一方法。它接受一个Action对象作为参数。该方法在发送Action的时候会自动调用Reducer并执行,因此,需要在store创建的时候传入Reducer函数。
import { createStore } from 'redux';
const store = createStore(reducer);
store.dispatch({
type: 'test',
payload: {}
});
store.subscribe()方法可以设置监听函数,用于监听State的变化。
import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);
一旦State发生变化,listener函数就会执行。如果将Redux的setState方法放入listener中,就可以将State变化与View关联起来,触发页面更新。
如果想要解除监听,可以调用这个方法的返回函数
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state; // 返回当前state
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());//state变化了,遍历执行所有listener中的函数
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);//取消监听时,从所有listener中剔除该listener
}
};
dispatch({});
return { getState, dispatch, subscribe };
};
为了方便在React项目中使用Redux,React的作者封装了一个库,就是React-Redux。实际项目中,你应该权衡一下,是直接使用 Redux,还是使用 React-Redux。后者虽然提供了便利,但是需要掌握额外的 API,并且要遵守它的组件拆分规范。
React-Redux 将所有组件分成两类:UI 组件(presentational component)和容器组件(container component)
UI 组件:顾名思义,就是不带有业务逻辑,仅作为展示用的组件。内部数据都由this.props来提供。
容器组件:与UI组件相对的就是容器组件,它负责管理数据和业务逻辑。
1. connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件
//TodoList是 UI 组件,ContentTodoList就是由 React-Redux 通过connect方法生成的容器组件。
import { connect } from 'react-redux'
const ContentTodoList = connect()(TodoList);
为了实现UI组件与容器组件之间的交互,就需要:①将state数据传递给UI组件。②把UI组件中用户的操作转化为Action传递到容器组件中。
为此,connect方法会接受mapStateToProps和mapDispatchToProps两个函数来分别做这两件事。
2. mapStateToProps()
用来建立state到UI组件的映射关系,接收State作为参数,执行后返回一个对象,其中的每一个键值对就是一个映射。
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
上述例子中的key值todos
表示是刚刚定义的TodoList UI组件所对应的数据,getVisibleTodos()
是一个函数,可以根据State来算出TodoList组件的数据值。例如
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
mapStateToProps会订阅Store,当State发生变化的时候,会自动触发,通过修改UI组件对应的数据参数,从而触发UI组件的重新渲染。
其实mapStateToProps还可以传入第二个参数,就是容器组件的Props,传入就会绑定容器组件的props,如果发生容器组件的参数改变,也会导致UI组件重新渲染。
3.mapDispatchToProps()
是connect的第二个参数,用来建立UI组件的参数到store.dispatch
方法的映射(定义了哪些操作会被当做Action来传递给Store)可以是函数或对象
const mapDispatchToProps = {
onClick: (filter) => {
type: 'Add_Todo',
filter: filter
};
}
4. Provider
通过connect创建的容器组件,需要能够拿到State对象进行操作,才可以生成UI组件需要的参数。
如果通过props一层层传递会比较麻烦,于是React-Redux 提供Provider组件,可以让容器组件拿到state。
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
//直接在跟组件 外面包裹,这样所有子组件都可以拿到State
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)