redux能解决啥问题?
redux的作用和vuex一样。 就是为了解决不同组件之间的通信。
基础版redux使用
首先先上一个没用redux的计算器demo
- 定义一个state:count 作为当前的值。 可对count做加1, 减1, 奇数加1, 异步加1 的操作 ,代码如下
import React, { Component } from 'react'
export default class Count extends Component {
state = {count:0}
//加法
increment = ()=>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({count:count+value*1})
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
const {count} = this.state
this.setState({count:count-value*1})
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
const {count} = this.state
if(count % 2 !== 0){
this.setState({count:count+value*1})
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
const {count} = this.state
setTimeout(()=>{
this.setState({count:count+value*1})
},500)
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
redux 精简版
提出redux概念
- 先上图, 把流程先跑一边, 并和vuex做一个区别,有利于理解
1)component组件作为吃饭客户, action作为服务员, store作为老板, reducers作为后厨。接着来跑一下流程 : 客户点了一份食品类型为 type :牛肉, data:3斤。 action作为服务员接受到这个信息并记录了一条订单返回一个对象 { type:“牛肉”, data :“3斤” }。并且通过dispatch(分发)订单给老板, 老板自然是不干活的, 就让后厨 reducers来进行处理,那么reducers是一个函数, 先会接收到两个参数当前的库存的值(previousState),以及传递过来的订单(action)。 reducers处理好数据后,返回最新的一个库存(state)的情况。并告诉给store。用户也可以直接获取到库存的数值。
redux精简版代码
代码结构
- store
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//暴露store
export default createStore(countReducer)
- 组件
- 通过dispatch来驱动
- 通过store.getState()来获取state的数据
import React, { Component } from 'react'
//引入store,用于获取redux中保存状态
import store from '../../redux/store'
export default class Count extends Component {
state = {carName:'奔驰c63'}
/* componentDidMount(){
//检测redux中状态的变化,只要变化,就调用render
store.subscribe(()=>{
this.setState({})
})
} */
//加法
increment = ()=>{
const {value} = this.selectNumber
store.dispatch({type:'increment',data:value*1})
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
store.dispatch({type:'decrement',data:value*1})
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
const count = store.getState()
if(count % 2 !== 0){
store.dispatch({type:'increment',data:value*1})
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
setTimeout(()=>{
store.dispatch({type:'increment',data:value*1})
},500)
}
render() {
return (
<div>
<h1>当前求和为:{store.getState()}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
- count_reducer
/*
1.该文件是用于创建一个为Count组件服务的reducer,reducer的本质就是一个函数
2.reducer函数会接到两个参数,分别为:之前的状态(preState),动作对象(action)
*/
const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
// console.log(preState);
//从action对象中获取:type、data
const {type,data} = action
//根据type决定如何加工数据
switch (type) {
case 'increment': //如果是加
return preState + data
case 'decrement': //若果是减
return preState - data
default:
return preState
}
}
- index.js
- redux数据更新默认不能驱动视图更新, 因此需要设定redux中state数据发生更新后, 视图也能更新
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
ReactDOM.render(<App/>,document.getElementById('root'))
// 订阅subscribe
store.subscribe(()=>{
ReactDOM.render(<App/>,document.getElementById('root'))
})
逐渐完善板块
- 上面的精简版少了action步骤,那就加上把 ~
1) constants.js
/*
该模块是用于定义action对象中type类型的常量值,目的只有一个:便于管理的同时防止程序员单词写错
*/
export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'
2)count_action.js
- 实际就是返回一个对象的函数
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
3)组件中
- 实际就是将dispatch中的对象换成一个执行的函数
- 处理下异步的事情吧?
- 异步其实就是让action返回的是函数, 然后通过dispatch交给reducer来处理。
1)action文件
/*
该文件专门为Count组件生成action对象
*/
import {INCREMENT,DECREMENT} from './constant'
//同步action,就是指action的值为Object类型的一般对象
export const createIncrementAction = data => ({type:INCREMENT,data})
export const createDecrementAction = data => ({type:DECREMENT,data})
//异步action,就是指action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(()=>{
dispatch(createIncrementAction(data))
},time)
}
}
2)组件
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
// setTimeout(()=>{
store.dispatch(createIncrementAsyncAction(value*1,500))
// },500)
}
3.)store.js
- 想使用异步需要重新配置store文件,安装redux-thunk, 并结合 applyMiddleware,thunk 来用
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './count_reducer'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//暴露store
export default createStore(countReducer,applyMiddleware(thunk))
react-redux基本概念
react-redux的使用可以大大提高开发效率。 但是存在这么一个情况:需要对上面的用法重新有一个新的概念 react-redux对组件提出了 容器组件和UI组件, 容器组件 : 操作redux的数据。并归类dispatch需要用到的方法,再将这些属性和方法传递给UI组件来使用。
1)容器组件
- connect 用于连接容器组件和UI组件的一个渠道。有两个括号connect ()() 第一个括号接收两个形参,为两个函数。 第一个形参 :用于传递获取到的state中的数据, 第二个形参 : 用于传递含有dispatch的方法。 第二个括号用来传UI组件。
//引入Count的UI组件
import CountUI from '../../components/Count'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
/*
1.mapStateToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){
return {count:state}
}
/*
1.mapDispatchToProps函数返回的是一个对象;
2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){
return {
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
2)UI组件接收数据
import React, { Component } from 'react'
export default class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
- 优化一下
1)将UI组件合并到容器组件中
2)将传递的两个形参简化一下
3)subcribe优化一下, 使用 Provider 来替代subcribe
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import store from './redux/store'
// 导入
import {Provider} from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
)
4)完整代码如下 :
import React, { Component } from 'react'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/count_action'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({count:state}),
//mapDispatchToProps的一般写法
/* dispatch => ({
jia:number => dispatch(createIncrementAction(number)),
jian:number => dispatch(createDecrementAction(number)),
jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),
}) */
//mapDispatchToProps的简写
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
数据共享版本
1)store.js
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
//引入为Count组件服务的reducer
import countReducer from './reducers/count'
//引入为Count组件服务的reducer
import personReducer from './reducers/person'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
he:countReducer,
rens:personReducer
})
//暴露store
export default createStore(allReducer,applyMiddleware(thunk))
2)count-组件
- 分批次获取
import React, { Component } from 'react'
//引入action
import {
createIncrementAction,
createDecrementAction,
createIncrementAsyncAction
} from '../../redux/actions/count'
//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//定义UI组件
class Count extends Component {
state = {carName:'奔驰c63'}
//加法
increment = ()=>{
const {value} = this.selectNumber
this.props.jia(value*1)
}
//减法
decrement = ()=>{
const {value} = this.selectNumber
this.props.jian(value*1)
}
//奇数再加
incrementIfOdd = ()=>{
const {value} = this.selectNumber
if(this.props.count % 2 !== 0){
this.props.jia(value*1)
}
}
//异步加
incrementAsync = ()=>{
const {value} = this.selectNumber
this.props.jiaAsync(value*1,500)
}
render() {
//console.log('UI组件接收到的props是',this.props);
return (
<div>
<h2>我是Count组件,下方组件总人数为:{this.props.renshu}</h2>
<h4>当前求和为:{this.props.count}</h4>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
)
}
}
//使用connect()()创建并暴露一个Count的容器组件
export default connect(
state => ({
count:state.he,
renshu:state.rens.length
}),
{
jia:createIncrementAction,
jian:createDecrementAction,
jiaAsync:createIncrementAsyncAction,
}
)(Count)
公司实际中的项目应用
1) 项目结构
- store: 定义store
import {applyMiddleware, createStore} from 'redux'
import reduxThunk from 'redux-thunk' //使store.dispatch()可以接受函数
import reducers from '../reducer'
const logger = store => next => action => {
if(typeof action === 'function'){
// console.log('dispatching a function')
}else{
console.log('dispatching a action: ' + action.type)
}
console.log('prevState', store.getState())
const result = next(action)
console.log('nextState', result)
return result
}
const middlewares = [
logger,
reduxThunk
]
// 2.创建store
const store = createStore(reducers, applyMiddleware(...middlewares))
export default store
reducer-index.js
import {combineReducers} from 'redux'
import work from './work'
import files from './files'
import api from './api'
import common from './common'
const index = combineReducers({
work: work,
files: files,
api: api,
common: common
})
export default index
reducer-files-index.js
import Types from "../../action/types";
const defaultState = {
files: [],
preUploadFile: null,
currFile: null,
uploadAgainFile: null,
successFileGuids: [],
selectedFile: {}
};
export default function onAction(state = defaultState, action) {
switch (action.type) {
case Types.FILES_CHANGE:
return {
...state,
files: action.files,
};
case Types.PREUPLOADFILE_CHANGE:
return {
...state,
preUploadFile: action.preUploadFile,
};
case Types.CURRFILE_CHANGE:
return {
...state,
currFile: action.currFile,
};
case Types.UPLOADAGAINFILE_CHANGE:
return {
...state,
uploadAgainFile: action.uploadAgainFile,
};
case Types.SUCCESSFILEGUIDS_CHANGE:
return {
...state,
successFileGuids: action.successFileGuids,
};
case Types.SELECTEDFILE_CHANGE:
return {
...state,
selectedFile: action.selectedFile,
};
default:
return state;
}
}
action-type.js
export default {
FILES_CHANGE: "FILES_CHANGE",
WORK_CHANGE: "WORK_CHANGE",
ADDMUSICAUDIOSTARTPLAYTIME_CHANGE: "ADDMUSICAUDIOSTARTPLAYTIME_CHANGE",
ADDMUSICAUDIOVOLUMEOBJ_CHANGE: "ADDMUSICAUDIOVOLUMEOBJ_CHANGE",
SUBTITLEWORKLIST_CHANGE: "SUBTITLEWORKLIST_CHANGE",
QUICKLYPOSITION_CHANGE: "QUICKLYPOSITION_CHANGE",
ADDWATERMARKWORKLIST_CHANGE: "ADDWATERMARKWORKLIST_CHANGE",
FETCHABORT_CHANGE: "FETCHABORT_CHANGE",
SUCCESSFILEGUIDS_CHANGE: "SUCCESSFILEGUIDS_CHANGE",
SELECTEDFILE_CHANGE: "SELECTEDFILE_CHANGE",
};
action-index.js
import {
onFilesChange,
onPreUploadFileChange,
onCurrFileChange,
onUploadAgainFileChange,
onSuccessFileGuidsChange,
onSelectedFileChange,
} from "./files";
import { onFileListModalChange, onTokenIdChange, onHandleImgErrChange, onUserActionChange } from "./common";
import {
onWorkChange,
onPrevWorkChange,
onCutWorkListChange,
onResolutionChange,
onVideoBgColorChange,
onUploadingChange,
onUploadPercentChange,
onProcessPercentChange,
onProcessStepChange,
onMergeWorkListChange,
onCurrProgressChange,
onStartTimeChange,
onCancelProgressChange,
onPlaybackRateChange,
onCropChange,
onRotateChange,
onFlipChange,
onFillingChange,
onRemoveWatermarkListChange,
onDragVideoPositionChange,
onFileToBlobStatusChange,
onSubmittingChange,
onUploadXHRChange,
onAddMusicWorkObjChange,
onAddMusicAudioStartPlayTimeChange,
onAddMusicAudioTimeObjChange,
onAddMusicAudioVolumeObjChange,
onSubtitleWorkListChange,
onAddWatermarkWorkListChange,
onQuicklyPositionChange,
onFetchAbortChange,
onLoopTimesChange,
onReverseObjChange,
onProcessModalVisibleChange,
} from "./work";
export default {
onFilesChange,
onWorkChange,
onPrevWorkChange,
onCutWorkListChange,
onMergeWorkListChange,
onResolutionChange,
onFileListModalChange,
onProcessPercentChange,
onVideoBgColorChange,
onTokenIdChange,
onPreUploadFileChange,
onUploadingChange,
onUploadPercentChange,
onProcessStepChange,
onHandleImgErrChange,
onCurrProgressChange,
onStartTimeChange,
onCurrFileChange,
onCancelProgressChange,
onUserActionChange,
onPlaybackRateChange,
onCropChange,
onRotateChange,
onFlipChange,
onFillingChange,
onRemoveWatermarkListChange,
onDragVideoPositionChange,
onFileToBlobStatusChange,
onSubmittingChange,
onUploadAgainFileChange,
onUploadXHRChange,
onAddMusicWorkObjChange,
onAddMusicAudioTimeObjChange,
onAddMusicAudioStartPlayTimeChange,
onAddMusicAudioVolumeObjChange,
onSubtitleWorkListChange,
onQuicklyPositionChange,
onAddWatermarkWorkListChange,
onFetchAbortChange,
onSuccessFileGuidsChange,
onSelectedFileChange,
onLoopTimesChange,
onReverseObjChange,
onProcessModalVisibleChange,
};
action-file-index.js
import Types from "../types";
export function onFilesChange(files) {
return { type: Types.FILES_CHANGE, files: files };
}
export function onPreUploadFileChange(preUploadFile) {
return { type: Types.PREUPLOADFILE_CHANGE, preUploadFile: preUploadFile };
}
export function onCurrFileChange(currFile) {
return { type: Types.CURRFILE_CHANGE, currFile: currFile };
}
export function onUploadAgainFileChange(uploadAgainFile) {
return { type: Types.UPLOADAGAINFILE_CHANGE, uploadAgainFile: uploadAgainFile };
}
export function onSuccessFileGuidsChange(successFileGuids) {
return { type: Types.SUCCESSFILEGUIDS_CHANGE, successFileGuids: successFileGuids };
}
export function onSelectedFileChange(selectedFile) {
return { type: Types.SELECTEDFILE_CHANGE, selectedFile: selectedFile };
}
入口函数index.js部分代码
ReactDOM.render(
<Provider store={store}>
<App lang={lang} />
</Provider>,
document.getElementById("hitpaw-root")
);
组件中使用