一. 纯原生 redux 的使用
Redux 是常用的状态管理容器, 能够帮助我们对一些全局变量进行有效管理. 首先, 举一个场景: 我正在开开心心的逛淘宝商城, 点进了一个淘宝店家的商品 A 的详情页, 觉得不错, 于是点击 "加入购物车", 接着我又点进了一个天猫店家的商品 B 的详情页, 也觉得不错, 又点击了 "加入购物车", 最终我打开购物车发现有 A B 两件商品. 假设淘宝商品详情页和天猫商品是两个相互独立的组件, 那么要实现上述场景, 购物车就应当是一个共享组件, 此时就可以使用 redux 对购物车实现统一管理. 那么以当前这个场景, 使用原生 redux 应该怎么实现呢? 废话不多说, 看如下实现:
安装 redux
redux 支持 NPM 等包管理工具, 故而进入项目目录中, 执行 NPM install redux --save
建立一个仓库来存储数据
- // store.JS
- import {createStore} from 'redux'
- // 声明一个实际的操作函数, 响应操作
- const cartReducer = (state = {
- goods: []
- }, action) => {
- switch (action.type) {
- case 'add':
- return {...state, goods: [...state.goods, action.good]}
- case 'delete':
- let deletedGoodIdx = action.good.index
- let newGoods = [...state.goods]
- newGoods.splice(deletedGoodIdx, 1)
- return {...state, goods: newGoods}
- default:
- return state
- }
- }
- // 创建 store 实例并导出
- const store = createStore(cartReducer)
- export default store
在组件中使用 store
读取: store.getState() 即可获取到 reducer 中的 state
修改: store.dispatch(action) 通过 dispatch 一个 action 来触发 reducer 中的修改行为
- /********** App.JS **************/
- import React from 'react'
- import {Link, Route} from 'react-router-dom'
- import store from './store'
- // 淘宝商品详情组件
- function TbItem() {
- return (
- <div>
- <h2 > 这是淘宝详情页</h2>
- <p > 当前购物车内商品数量:{store.getState().goods.length}</p>
- <button onClick={() => store.dispatch({type: 'add', good: {title: ` 淘宝商品 ${Date.now()}`, price: 100}})}>添加购物车</button>
- </div>
- )
- }
- // 天猫商品详情组件
- function TmItem() {
- return (
- <div>
- <h2 > 这是天猫详情页</h2>
- <p > 当前购物车内商品数量:{store.getState().goods.length}</p>
- <button onClick={() => store.dispatch({type: 'add', good: {title: ` 天猫商品 ${Date.now()}`, price: 200}})}>添加购物车</button>
- </div>
- )
- }
- // 购物车组件
- function Cart() {
- let goods = store.getState().goods
- return (
- <ul>
- {
- goods.map((good, index) => {
- return (
- <li key={index}>
- <span>{good.title}</span>
- <button onClick={() => store.dispatch({type: 'delete', good: {index}})}>删除</button>
- </li>
- )
- })
- }
- </ul>
- )
- }
- // 使用 react-router-dom 配置两个页面
- function App() {
- return (
- <div className="App">
- <div>
- <Link to="/tb">淘宝</Link> |
- <Link to="/tm">天猫</Link>
- </div>
- <Route path="/tb" component={TbItem}></Route>
- <Route path="/tm" component={TmItem}></Route>
- <Cart></Cart>
- </div>
- );
- }
- export default App
在 index.JS 中订阅 store
一旦 store 发生更改, 则重新 render 整个 App
- /*************index.JS***************/
- import React from 'react'
- import ReactDOM from 'react-dom'
- import App from './App'
- import store from './store'
- import {BrowserRouter} from 'react-router-dom'
- const render = () => {
- ReactDOM.render(
- <BrowserRouter>
- <App />
- </BrowserRouter>,
- document.getElementById('root')
- )
- }
- // 第一次启动时, 直接渲染
- render()
- // 监听 store 的变化
- store.subscribe(render)
二. react-redux 及中间件使用
从上面的例子中可以看到, 使用原生的 redux 对 store 的读取和操作都比较麻烦, 同时还需要在 index.JS 上手动调用渲染函数. 因此, 在实际项目中, 通常使用 react-redux 库结合 redux-thunk 中间件 (用于异步操作) 来管理状态, 同样以上述中的例子, 看看 react-redux 的实现有什么不同.
安装
NPM install react-redux redux-thunk --save
同样需要创建一个仓库来存储
一般将 reducer 独立为一个文件书写, 使代码结构更加清晰
- /************cart.redux.JS************/
- // cartReducer 与上一章节保持一致
- const cartReducer = (state = {
- goods: []
- }, action) => {
- // ...
- }
- // 定义 action 操作
- const addGood = (good) => ({type: 'add', good})
- const deleteGood = (good) => ({type: 'delete', good})
- // 异步 action, 使用 thunk 中间件之后可以如下书写
- const asyncAddd = (good) => dispatch => {
- setTimeout(() => {
- dispatch({type: 'add', good})
- }, 2000)
- }
- // 将 reducer 和 action 方法导出
- export {cartReducer, addGood, deleteGood, asyncAddd}
使用 Provider 包裹 App, 并传递 store 参数
- /****************index.JS******************/
- import React from 'react'
- import ReactDOM from 'react-dom'
- import App from './App'
- import {BrowserRouter} from 'react-router-dom'
- import {Provider} from 'react-redux'
- import {createStore, applyMiddleware} from 'redux'
- import thunk from 'redux-thunk'
- import {counterReducer} from './cart.redux'
- const store = createStore(counterReducer, applyMiddleware(thunk))
- ReactDom.render(
- <BrowserRouter>
- <Provider store={store}>
- <App />
- </Provider>
- </BrowserRouter>,
- document.querySelector('#root')
- )
使用 connect 高阶组件加工 App 组件
- /****************App.JS******************/
- // ...
- import {connect} from 'react-redux'
- import {addGood, deleteGood, asyncAdd} from './reactRedux/cart.redux'
- function App() {
- // ...
- }
- App = connect(
- state => ({goods: state.goods}),
- {addGood, deleteGood, asyncAdd}
- )(App)
- export default App
也可以配置装饰器模式, 采用装饰器模式书写:
- @connect(
- state => ({goods: state.goods}),
- {addGood, deleteGood, asyncAdd}
- )
- // 使用装饰器时需要用 class 声明组件
- class App extends React.Component {
- // ...
- }
在 App 组件内需要读取商品列表可采用 this.props.goods 获取, 需要添加商品则可以调用 this.props.addGood(good), 需要删除商品时则可以调用 this.props.deleteGood(good). 至于如何将参数传递给子组件 TbItem 和 TmItem 则有三种实现方案:
使用父子组件通信的方法, 通过 <Provider></Provider> <Consumer></Consumer> 实现;
同样用 connect 高阶组件装饰一下组件, 组件内通过 this.props 获取 store 中的属性和方法;
利用 context 实现, 在 Parenet 组件中定义 静态属性 childContextTypes 和 getChildContext 方法, Child 组件中定义 静态属性 contextTypes 并在构造函数中将 context 传递给 super()函数
- /**************** 父组件 ******************/
- // ...
- import PropTypes from 'prop-types'
- class App extends React.Component {
- static childContextTypes = {
- goods: PropTypes.object
- }
- getChildContext() {
- return { goods: this.props.goods };
- }
- // ...
- }
- /**************** 子组件 ******************/
- class TbItem extends React.Component {
- static contextTypes = {
- goods: PropTypes.object,
- }
- constructor(props, context){
- super(props, context)
- // ...
- }
- // ...
- }
来源: http://www.jianshu.com/p/bdebab039ad0