Redux:如果你不知道是否需要 Redux,那就是不需要它。
redux类似于vuejs的vuex,是react的数据管理中心。
-
redux上手
-
首先我们需要安装redux
npm install redux --save 复制代码
-
-
Store
redux 中我们首先需要理解store,这个就是帮我们管理数据的政委,Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
-
State
Store
对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。当前时刻的 State,可以通过store.getState()
拿到 -
Action
State 的变化,会导致 View 的变化。但是,用户不能够直接改变State,只能操控View所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
Action 是一个对象。其中的
type
属性是必须的,表示 Action 的名称。 -
Dispatch
功能就是我们View层要发出Action,必须要通过dispatch来进行触发
-
Reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
-
对redux通俗理解
就拿我们 * 东来说,发货都是从仓库发出,仓库就是redux的store,store中的state是存储数据也就是相当于仓库的货物,当我们从*东软件上下单 就是我们触发一个dispatch,然后 *东后台接到订单通知(action)仓库,相当我们下单买的货物的通知到达仓库(这个过程是我们和货物不能直接接触的,如果能接触货物早就偷光了哈哈哈),通知到仓库后,仓库里边有工人(reducer)来进行处理发货一系列的动作,发出货物之后,仓库货物的数据(state数据)就变了,货物到我们手里(view也就变了)
-
简单加减按钮的实现
// store.js import {createStore} from 'redux' const counterReducer = (state=0,action)=>{ // 设置默认值 switch (action.type) { case 'add': return state+1 ; // 一定是返回新的state 而不是像vue直接修改 case 'minus': return state-1 ; default: return state } }; const store = createStore(counterReducer); export default store 复制代码
// App.js class App extends Component { render() { return ( <div className="App"> {/*getState()返回当前的state树它与 store 的最后一个 reducer 返回值相同。*/} <p>{store.getState()}</p> <Button type={"primary"} onClick={()=>store.dispatch({type:"add"})} >增加+</Button> <Button type={"primary"} onClick={()=>store.dispatch({type:"minus"})}>减少-</Button> </div> ); } } 复制代码
// index.js 中的render方法需要自己手动订阅监听 import store from "./store"; const render =()=>{ ReactDOM.render( <App />, document.getElementById('root') ); }; render(); store.subscribe(render); // 每次 dispatch也就是state发生变化都需要render一次 复制代码
-
react-redux
上边使用redux 每次都需要render麻烦且不优雅,使用react-redux更好,不用手动再去监听
npm insatll react-redux --save 复制代码
- Provider顶级组件,来提供数据,
- connect高阶组件,提供数据和方法,把组件的状态映射到属性之上,然后只用props来进行调用
- 看看react-redux如何实现上边的功能,
// index.js 不需要手动再去监听 ,直接使用Provider进行包裹,自动监听数据了 import store from "./store"; import {Provider} from 'react-redux' ReactDOM.render( // 外层包一层Provider ,提供数据和组件间的数据传递一样 <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); 复制代码
Provider将数据传递进来数据任何地方想使用就用connect链接,这样App和redux就链接起来了
// app.js class App extends Component { render() { return ( <div className="App"> <p>{this.props.num}</p> // dispatch 直接写对应的方法 <Button type={"primary"} onClick={()=>this.props.add()}>增加+</Button> <Button type={"primary"} onClick={()=>this.props.minus()}>减少-</Button> </div> ); } } const mapStateToProps = (state) =>{ // 它把状态映射到属性之上 所以上边使用this.props return { num:state } }; const mapDispatchToProps = dispatch=>{ return{ add:()=>dispatch({type:"add"}), minus:()=>dispatch({type:"minus"}) } } App = connect(mapStateToProps,mapDispatchToProps)(App) 复制代码
-
react-redux中的connect 使用装饰器写法
上边的connect 使用的高阶组件来声明需要的state里边的数据和方法
//App.js @connect( // Es6语法 //state=>({num:state}), // 读数据 state=>{ return { num:state } }, //除了ES6语法简写外 同步的dispatch可以进行简写 //写数据,要调用的dispatch 并且这些方法会传递到props中 // add:()=>({type:"add",step:2}), 可以传递多个参数,但第一参数是修改数据的请求 { add:()=>({type:"add"}), minus:()=>({type:"minus"}) } // dispatch=>{ // return{ // add:()=>dispatch({type:"add"}), // minus:()=>dispatch({type:"minus"}) // } // } ) 复制代码
-
redux默认都是同步的操作,这样异步任务就需要使用 中间件 来完成
为什么异步任务只能通过中间件来完成?
- Reducer :纯函数,只能承担计算State的功能,不适合承担其他功能,理论上纯函数不能进行读写操作
- View:与State一一对应,可以看做State的视觉层,也不适合承担其他功能
- Action:存放数据的对象,即消息的载体,只能被别人操控,自己并不能进行任何操作
- 实际的reducer和action store都需要独立拆分文件
所以要进行异步任务的dispatch请求,到达Reducer之前都需要经过中间件的处理。
我们试一下redux-logger和redux-thunk(异步操作) 两个中间件
//store.js import {createStore,applyMiddleware} from 'redux' // applyMiddleware 是使用中间件 import logger from 'redux-logger' import thunk from 'redux-thunk' const counterReducer = (state=0,action)=>{ // 设置默认值 switch (action.type) { case 'add': return state+1 ; case 'minus': return state-1 ; default: return state } }; const store = createStore(counterReducer,applyMiddleware(logger,thunk)); 复制代码
@connect( state=>{ return { num:state } }, { add:()=>({type:"add"}), minus:()=>({type:"minus"}), asyncAdd:()=>dispatch=>{ // 延时加1。模拟异步任务 setTimeout(()=>{ dispatch({type:"add"}) },2000) } } ) 复制代码
-
多个reducer
//store.js import {createStore,applyMiddleware,combineReducers} from 'redux' const store = createStore( combineReducers({counterReducer,numberReducer}), // 假如我们还有一个numberReducer applyMiddleware(logger,thunk) ); export default store 复制代码
-
抽离Reducer 和Action
// 新建 reducer.redux.js const counterReducer = (state=0,action)=>{ // 设置默认值 switch (action.type) { case 'add': return state+1 ; case 'minus': return state-1 ; default: return state } }; const add= ()=>({type:"add"}); const minus=()=>({type:"minus"}); // 支持这种返回一个函数的写法,就是因为用了thunk const asyncAdd =()=>dispatch=>{ setTimeout(()=>{ dispatch({type:"add"}) },2000) }; export {counterReducer,add,minus,asyncAdd} 复制代码
import {Provider} from 'react-redux' import * as serviceWorker from './serviceWorker'; import {createStore,applyMiddleware} from 'redux' import logger from 'redux-logger' import thunk from 'redux-thunk' import {counterReducer} from "./couter.redux"; // 新建store 第一个参数reducer 第二个参数中间件 const store = createStore(counterReducer,applyMiddleware(logger,thunk)); //import store from "./store"; store.js文件就没有什么用了 ReactDOM.render( // 外层包一层Provider ,提供数据和组件间的数据传递一样 <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); 复制代码
// App.js 直接引入reducer.redux.js的方法 import {add,minus,asyncAdd} from "./couter.redux"; @connect( state=>{ return { num:state } }, {add,minus,asyncAdd} // ) 复制代码