前言
学习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,欢迎来骚,一起学习。
最后的最后
火星大仙镇贴