react redux react-redux redux-thunk
react --> UI
redux --> 存储数据(可以理解为一个数据库)
react-redux --> redux搭配react使用的库(redux写起来比较麻烦,react-redux封装好一些方法便于开发)
redux-thunk --> 中间件,可以dispatch异步请求
redux
redux中的reducer为什么是纯函数?
不考虑性能的话,写不纯的reducer来变动数据在技术上是可行的,但并不鼓励这么做。不纯的reducer会使得一些开发特性,如时间旅行、记录/回访或热加载不可实现。此外,这种数据不可变动的特性, 不会产生性能问题,即使对象分配失败,仍然可以防止昂贵的冲渲染和计算,这得益于reducer的纯度,应用内的变化更是一目了然
使用tips
- 应该尽量减少在action中传递的数据
- 保持reducer的纯净,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
- 在reducer中,不要修改state(时刻谨记永远不要在克隆 state 前修改它)。使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { …state, …newState } 达到相同的目的
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
- createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
完整参数:createStore(reducer, defaultState, applyMiddleware(thunk))
一般使用形式:createStore(reducer, applyMiddleware(thunk))
let store = createStore(todoApp, window.STATE_FROM_SERVER)
redux写法
import { createStore, combineReducers } from 'redux';
const initState = { count: 1 };
// reducer
const counter = function( state = initState, action ){
let count = state.count // 1
switch(action.type){
case 'INCREMENT':
count++;
return { count };
case 'DECREMENT':
count--;
return { count };
default:
return state
}
}
// reducer组合
const reducers = combineReducers({ counter: counter });
// 创建store
let store = createStore(reducers)
export default store
// 监听数据变化,返回注销监听的方法
const unsubscribe = store.subscibe(() => {
// 获取state
console.log(store.getState()) // { counter: { count: 1 } } 格式:{ ReducersName: { 对应的state } }
})
// 派发action 修改state
store.dispatch({ type: 'INCREMENT', payload: { count: 10 } });
react-redux
tips
- 容器组件和展示组件相分离
a. 容器组件用来请求数据(描述如何运行(数据获取、状态更新)),展示组件注重UI展示(描述如何展现(骨架、样式)),这样一来就使得展示组件可以复用
b. 技术上讲,容器组件就是使用store.subscribe()从Redux state树中读取部分数据,并通过props来把这些数据提供给要渲染的组件。但建议使用react-redux库的connect()方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染 - 建议使用指定的 React Redux 组件 来让所有容器组件都可以访问 store,而不必显示地传递它。只需要在渲染根组件时使用即可
// index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './components/App'
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
- 根路径("/")传参
//这使得可以在 App 中获取 params 的属性。params 是一个包含 url 中所有指定参数的对象。 例如:如果我们访问 localhost:3000/completed,那么 params 将等价于 { filter: 'completed' }。
<Route path="/(:filter)" component={App} />
// App.js
const App = ({ params }) => {
return (
<div>
<AddTodo />
<VisibleTodoList
filter={params.filter || 'all'}
/>
<Footer />
</div>
);
};
react-redux使用
import { connect } from 'react-redux';
function App(props){
const { count, add, sub } = props;
return (
<div className="App">
<button onClick={add}>加1</button>
<hr></hr>
<button onClick={sub}>减1</button>
<hr></hr>
<span>{count}</span>
</div>
)
}
const mapStateToProps = (state) => {
let count = state.counter.count
return {
count
}
}
const mapDispatchToProps = dispatch => ({
add: () => {
dispatch({ type: 'INCREMENT' });
},
sub: () => {
dispatch({ type: 'DECREMENT' });
},
})
export default connect(mapStateToProps, mapDispatchToProps)(App)
redux异步解决方案
- Redux本身只能处理同步的Action,但可以通过中间件来拦截处理其它类型的action,比如函数(Thunk),再用回调触发普通Action,从而实现异步处理,在这点上所有Redux的异步方案都是类似的。
-
使用redux-thunk中间件
(本质:通过使用指定的 middleware,action 创建函数除了返回 action 对象外还可以返回函数。这时,这个 action 创建函数就成为了 thunk–原先的action是对象({type:’’,payload:{}})的形式,加入中间件之后,可以将action写为函数形式,也就是说可以dispatch一个带有副作用的函数)
// thunk 的一个优点是它的结果可以再次被 dispatch
import thunkMiddleware from 'redux-thunk';
import { createLogger } from 'redux-logger';
const loggerMiddleware = createLogger();
// 使用thunk中间件(这样一来就可以dispatch带有副作用的函数了)
let store = createStore(reduces, applyMiddleware(thunkMiddleware,loggerMiddleware))
// connect进component的函数
const mapDispatchToProps = (dispatch) => {
return {
add: () => {
dispatch({ type: 'INCREMENT' });
},
sub: () => {
dispatch({ type: 'DECREMENT' });
},
getMenu: () => { // 使用thunk
dispatch((dispatch) => { // dispatch函数
axios.get('xxxxx').then((res) => {
dispatch({ // 这里可以二次dispatch
type: 'DECREMENT',
});
});
});
},
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
-
Thunk middleware 并不是 Redux 处理异步 action 的唯一方式:
可以使用 redux-promise 或者 redux-promise-middleware 来 dispatch Promise 来替代函数。
可以使用 redux-observable 来 dispatch Observable。
可以使用 redux-saga 中间件来创建更加复杂的异步 action。
可以使用 redux-pack 中间件 dispatch 基于 Promise 的异步 Action。 -
Monkeypatch
// 实现每次更新store打印日志的效果 // 直接修改 store 实例中的 dispatch 函数 let next = store.dispatch store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result }