Redux使用篇

前言

学习redux首先要知道redux是什么。官网介绍的redux是js状态容器。黑人问号脸???

在我的理解中,redux就是管理state(状态)的仓库。你想想在做react项目中,数据的传递是通过父组件向子组件通过prop一层层传递的。或者子组件向父组件传递通过事件一层层传递。在大型项目中往往都是一个组件嵌套一个组件再嵌套很多个组件。而redux的出现就能让数据在各个组件流动中畅通无阻。redux也不是react专用,你甚至可以使用在vue中

react项目中也可以不用redux。或者小项目你也可以用Pubsub(pubsub-js)的方法也是可以的(直接通过事件来监听数据变化),不过你必须在componentWillUnmount中销毁事件(其实你组件内的所有添加的事件监听或者定时器一般都要在componentWillUnmount销毁),也可以使用Moby。React的新Context
Api解决了跨组件传参泛滥的问题。

OK,废话不多说,直接开搞。

01.redux四个主要的步骤

reducer,createstore,subscribe,dispatch
! 必须要了解这四个概念
-1.计算定义规则,既reducer
-2.第二部根据计算规则生成 store,即createstore
-3.第三部定义数据(既state)变化之后的派发规则(监听变化),即subscribe
-4.第四步触发数据变化,派发事件,即dispatch

来人,上代码:

import { createStore } from 'redux' 

import { createStore } from 'redux'

export default function () {
  // 第一步: 计算定义规则,既reducer
  // reducer建立
  // 根据老的state和action生成新的state
  function counter(state = 0, action) {
    switch (action.type) {
      case 'INCREMENT':
        return state + 1
      case 'DECREMENT':
        return state - 1
      default:
        return 10
    }
  }
  // 第二部根据计算规则生成 store
  const store = createStore(counter)

  // 第三部定义数据(既state)变化之后的派发规则(监听变化)
  store.subscribe(() => {
    console.log('fn1_current state', store.getState())
  })

  store.subscribe(() => {
    console.log('fn2_current state', store.getState())
  })
  // 第四步触发数据变化,派发事件
  store.dispatch({ type: 'INCREMENT' })
  store.dispatch({ type: 'INCREMENT' })
  store.dispatch({ type: 'DECREMENT' })
} 

ok,下面来一段文字简单描述redux整个流程:

下面我说的reducer和Reducer,action和Action是不同意思,比如Action代表函数,action代表对象
首先通过reducer建立store,随时通过store.getState获取状态,store.dispatch(action)来修改状态(! dispach并没有直接修改state仅仅只是追踪state的变化,你可以理解为异步的),然后Reducer函数接受state和action,返回新的state。使用store.subscribe进行监听每次的修改。

redux和react一起使用

先想一下将redux运用在react的关键点是什么。可以从组件上,react的Dom的渲染更新机制来考虑。先停3分钟
······
······
······
Ok,关键就是dispacth和subscribe。
如何将dispacth传给组件,让其可以在内部修改。
如何让subscribe订阅render函数,每次更新都会重新渲染。

其次redux默认是处理同步任务的,如果需要处理异步任务需要使用到react-thunk中间件。使用redux中的applyMiddleWare开启中间件thunk,可以创建Action函数,返回一个函数而不是返回action。
eg.

//setTimeout模拟异步方法
export function addAsync(){
   return dispatch => {
       setTimeout(()=>{
           dispatch(Action())
       },2000)
   }
}

这个代码片段不知道什么意思,不要紧,下面上完整代码。
首先创建store让其和react关联。

import React from 'react'
import ReactDOM from 'react-dom'
import thunk from 'redux-thunk'
import { BrowserRouter } from 'react-router-dom'
import { createStore, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import { AppContainer } from 'react-hot-loader' //eslint-disable-line
import App from './views/App'
import Reducers from './store/reducer'
// import { counter } from './reducers/index.redux'

//const reduxDevTools = window.devToolsExtension
const store = createStore(
  Reducers,
  //reduxDevTools ? reduxDevTools() : undefined,
  compose(applyMiddleware(thunk)),
)

const root = document.getElementById('root')
const render = (Compoment) => {
  ReactDOM.render(
    <AppContainer>
      <Provider store={store}>
        <BrowserRouter>
          <Compoment />
        </BrowserRouter>
      </Provider>
    </AppContainer>,
    root,
  )
}
render(App)
if (module.hot) {
  module.hot.accept('./views/App.jsx', () => {
      const NextApp = require('./views/App.jsx').default //eslint-disable-line
    render(NextApp)
  })
}

上面可以看到这是创建了一个store,
const store = createStore(
Reducers,
compose(applyMiddleware(thunk)),
)
通过applyMiddleware开启了thunk。使用react-redux中的Provider 的Api将store传给各个组件
而 './store/reducer'文件是合并了多个reducer。这个的理解就是不管是vuex或者redux也好都是单一的状态树,也就是说每个应用中只有一个store管理所有的state。
下面是配置代码,这里一个有两个我的reducer--counter和auth

// 合并所有的reducer,并且返回
import { combineReducers } from 'redux'
import { counter } from '../reducers/index.redux'
import { auth } from '../reducers/Auth.redux'

export default combineReducers({ counter, auth })

好,下面我们看counter reducer

const ADD_NUM = 'INCREMENT'
const REMOVE_NUM = 'DECREMENT'
// 第一步: 计算定义规则,既reducer。这里创建一个Reducer函数
export function counter(state = { num: 1 }, action) {
  switch (action.type) {
    case ADD_NUM:
      return Object.assign({}, state, { num: state.num+1 })
    case REMOVE_NUM:
      return Object.assign(state, { num: state.num-1 })
    default:
      return state
  }
}

// Action creator
export function addNum() {
  return { type: ADD_NUM }
}

export function removeNum() {
  return { type: REMOVE_NUM }
}

// 模拟异步
export function addNumAsync() {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(addNum())
    }, 2000)
  }
}

从export function addNum()可以看出action不再是一个对象,而是作为函数返回了。
而export function addNumAsync()相当于将dispatch传给组件了,你也可以将这方法直接写在组件上。

然后下面的代码就是将subscribe订阅到render函数上了我用一个demo组件为例子

import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { addNum, addNumAsync } from '../reducers/index.redux'

// const mapStateoProps = state => ({ num: state })
// const actionCreators = { addNum, addGunAsync }
class Dome extends React.Component {
  componentDidMount() {
    // do something
  }
  render() {
    return (
      <div>
        <h2>酒数量{this.props.num}</h2>
        <button onClick={this.props.addNum}>增加酒</button>
        <button onClick={this.props.addNumAsync}>不急过两天在增加</button>
      </div>
    )
  }
}
const mapStateoProps = state => ({ num: state })
const actionCreators = { addNum }
Dome = connect(mapStateoProps, actionCreators)(Dome)
export default Dome

Dome.propTypes = {
  num: PropTypes.number,
  addNum: PropTypes.func,
  addNumAsync: PropTypes.func,
}

这里你可以看到使用了react-redux的connect将subscribe订阅到了组件的render函数上了
const mapStateoProps = state => ({ num: state })
const actionCreators = { addNum }
Dome = connect(mapStateoProps, actionCreators)(Dome)

最后我建议使用装饰器的写法,会让代码更加美观简便些

import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { addNum, addNumAsync } from '../reducers/index.redux'

// const mapStateoProps = state => ({ num: state })
// const actionCreators = { addNum, addGunAsync }
@connect(
  state => ({ num: state.counter }), // 相当(state) => { return ({num:state}) }
  { addNum, addNumAsync },
)
class Dome extends React.Component {
  componentDidMount() {
    // do something
  }
  render() {
    return (
      <div>
        <h2>拥有机关枪{this.props.num}</h2>
        <button onClick={this.props.addNum}>增加武器库</button>
        <button onClick={this.props.addNumAsync}>过两天在增加</button>
      </div>
    )
  }
}
export default Dome

Dome.propTypes = {
  num: PropTypes.number,
  addNum: PropTypes.func,
  addNumAsync: PropTypes.func,
}

这种方法需要配置webpack的plugin,引入"babel-plugin-transform-decorators-legacy"依赖,我就不详细说了。

ok最后实现一个简单的登录注册的代码片段

import axios from 'axios'
import { getReirectPath } from '../util/util'

const REGISTER_SUCCESS = 'REGISTER_SUCCESS'
const ERROR_MSG = 'ERROR_MSG'
const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
const LOAD_DATA = 'LOAD_DATA'

const initState = {
  redirectTo: '',
  isAuth: false,
  msg: '',
  userName: '',
  type: '',
}

// reducer
export function user(state = initState, action) {
  switch (action.type) {
    case REGISTER_SUCCESS:
      return Object.assign(
        {},
        state,
        { isAuth: true, redirectTo: getReirectPath(action.payload) },
        action.payload,
      )
    case LOAD_DATA:
      return Object.assign(
        {},
        state,
        { isAuth: true, redirectTo: getReirectPath(action.payload) },
        action.payload,
      )
    case LOGIN_SUCCESS:
      return Object.assign(
        {},
        state,
        { isAuth: true, redirectTo: getReirectPath(action.payload) },
        action.payload,
      )
    case ERROR_MSG:
      return Object.assign({}, state, { isAuth: false, msg: action.umsg })
    default:
      return state
  }
}


function registerSuccess(data) {
  return {
    type: REGISTER_SUCCESS,
    payload: data,
  }
}

function loginSuccess(data) {
  return { type: LOGIN_SUCCESS, payload: data }
}

function errorMsg(msg) { return { type: ERROR_MSG, umsg: msg } }

// 获取用户信息
export function loadData(userinfo) {
  return { type: LOAD_DATA, payload: userinfo }
}
// action
// 注册
export function regisger({
  userName, password, repeatpwd, type,
}) {
  if (!userName || !type) {
    return errorMsg('用户名必须输入')
  }
  if (!password || !type) {
    return errorMsg('密码必须输入')
  }
  if (password !== repeatpwd) {
    return errorMsg('密码和确定密码不相同')
  }
  return (dispatch) => {
    axios.post('/user/register', { userName, password, type })
      .then((res) => {
        if (res.status === 200 && res.data.code === 0) {
          dispatch(registerSuccess(res.data.data))
        } else {
          dispatch(errorMsg(res.data.msg))
        }
      })
  }
}

// 登录
export function LoginEvent({ userName, password }) {
  if (!userName) {
    return errorMsg('用户名必须输入')
  }
  if (!password) {
    return errorMsg('密码必须输入')
  }
  return (dispatch) => {
    axios.post('user/login', { userName, password })
      .then((res) => {
        if (res.status === 200 && res.data.code === 0) {
          dispatch(loginSuccess(res.data.data))
        } else {
          dispatch(errorMsg(res.data.msg))
        }
      })
  }
}

最后

这是我第一次写相关的文章,都是个人理解角度入手,可能存在不正确的名词或者某些方面理解错误。请帮我指正出来,我会在后面慢慢修改。如果你觉得有用,欢迎随便转载,请备注作者,谢谢。如果反响可以我会再写进阶篇。
上面代码一行行敲出来,亲测可用。登录那块我是使用node来写后台提所有数据接口,另外我是真的喜欢axios。
但是写法是有问题的,比如Action这一块如果业务多起来,需要一个一个Action声明。所有建议将Action进行封装。
另外推荐大家一个react框架taro,欢迎来骚,一起学习。

最后的最后

火星大仙镇贴


timg (3).jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,702评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,143评论 3 415
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,553评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,620评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,416评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,940评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,024评论 3 440
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,170评论 0 287
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,709评论 1 333
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,597评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,784评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,291评论 5 357
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,029评论 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,407评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,663评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,403评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,746评论 2 370