1.同步解决共享数据
问题引入,我最终想实现的效果如下
方案一:原生Redux
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './pages/Vote';
import reportWebVitals from './reportWebVitals';
import { createStore } from 'redux'
const initial = {
title: 'React vs Vue 哪家强?',
suppNum: 0,
oppNum: 0
}
function reducer(state = initial, action) {
switch (action.type) {
case 'SUPPORT':
state.suppNum += action.payload;
break;
case 'OPPOSE':
state.oppNum += action.payload;
break;
default:
break;
}
return state
}
const store = createStore(reducer)
ReactDOM.render(
<React.StrictMode>
<App store={store} />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
// vote.js
import React, { Component } from 'react'
import VoteHeader from './VoteHeader'
import VoteBody from './VoteBody'
import VoteFooter from './VoteFooter'
export default class Vote extends Component {
render() {
const { store } = this.props
return (
<div>
<VoteHeader store={store} />
<VoteBody store={store} />
<VoteFooter store={store} />
</div>
)
}
}
//VoteHeader.jsx
import React, { Component } from 'react'
export default class VoteHeader extends Component {
componentDidMount(){
const { store } = this.props;
// 订阅事件
store.subscribe(()=>this.forceUpdate())
}
render() {
const { store } = this.props;
const { title, suppNum, oppNum } = store.getState();
return (
<div>
<h1>{title}</h1>
<p>NUM:{suppNum + oppNum}</p>
</div>
)
}
}
// VoteBody.jsx
import React, { Component } from 'react'
export default class VoteBody extends Component {
constructor(props) {
super(props)
const { store } = this.props
const { suppNum, oppNum } = store.getState()
this.state = {
suppNum,
oppNum
}
}
componentDidMount() {
const { store } = this.props
// 订阅事件
store.subscribe(() => {
console.log(store.getState())// {title: 'React vs Vue 哪家强?', suppNum: 0, oppNum: 0 }
const { suppNum, oppNum } = store.getState()
// 自己做个渲染优化吧
if (suppNum !== this.state.suppNum) {
this.setState({
suppNum: suppNum
})
}
if (oppNum !== this.state.oppNum) {
this.setState({
oppNum: oppNum
})
}
})
}
render() {
const { suppNum, oppNum } = this.state
return (
<div>
<p>suppNum:{suppNum}</p>
<p>oppNum:{oppNum}</p>
</div>
)
}
}
// VoteFooter.jsx
import React, { Component } from 'react'
export default class VoteFooter extends Component {
render() {
return (
<div>
<button onClick={()=>this.handler(0)}>支持</button>
<button onClick={()=>this.handler(1)}>反对</button>
</div>
)
}
handler = type => {
console.log(type)
const { store } = this.props
if(type === 0 ){
store.dispatch({
type: 'SUPPORT',
payload: 10
})
return
}
store.dispatch({
type: 'OPPOSE',
payload: 5
})
}
}
方案二:Redux工程化解决方案
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './pages/Vote';
import reportWebVitals from './reportWebVitals';
import store from './store';
ReactDOM.render(
<React.StrictMode>
<App store={store} />
</React.StrictMode>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
vote.jsx
import React, { Component } from 'react'
import VoteHeader from './VoteHeader'
import VoteBody from './VoteBody'
import VoteFooter from './VoteFooter'
export default class Vote extends Component {
render() {
const { store } = this.props
return (
<div>
<VoteHeader store={store} />
<VoteBody store={store} />
<VoteFooter store={store} />
</div>
)
}
}
VoteHeader.jsx
import React, { Component } from 'react'
export default class VoteHeader extends Component {
componentDidMount(){
const { store } = this.props;
// 订阅事件
store.subscribe(()=>this.forceUpdate())
}
render() {
const { store } = this.props;
// 解构时后面需要从vote模块获取
const { title, suppNum, oppNum } = store.getState().vote;
return (
<div>
<h1>{title}</h1>
<p>NUM:{suppNum + oppNum}</p>
</div>
)
}
}
VoteBody.jsx
import React, { Component } from 'react'
export default class VoteBody extends Component {
constructor(props) {
super(props)
const { store } = this.props
console.log(store.getState(),'store')
const { suppNum, oppNum } = store.getState().vote
this.state = {
suppNum,
oppNum
}
}
componentDidMount() {
const { store } = this.props
// 订阅事件
store.subscribe(() => {
console.log(store.getState().vote)// {title: 'React vs Vue 哪家强?', suppNum: 0, oppNum: 0 }
// 解构时后面需要从vote模块获取
const { suppNum, oppNum } = store.getState().vote
// 自己做个渲染优化吧
if (suppNum !== this.state.suppNum) {
this.setState({
suppNum: suppNum
})
}
if (oppNum !== this.state.oppNum) {
this.setState({
oppNum: oppNum
})
}
})
}
render() {
const { suppNum, oppNum } = this.state
return (
<div>
<p>suppNum:{suppNum}</p>
<p>oppNum:{oppNum}</p>
</div>
)
}
}
VoteFooter.jsx
import React, { Component } from 'react'
import action from '../store/actions'
export default class VoteFooter extends Component {
render() {
return (
<div>
<button onClick={() => this.handler(0)}>支持</button>
<button onClick={() => this.handler(1)}>反对</button>
</div>
)
}
handler = type => {
console.log(type)
const { store } = this.props
if (type === 0) {
store.dispatch(action.vote.support(10))
return
}
store.dispatch(action.vote.oppose(5))
}
}
store->actions
// voteAction.js
import * as TYPES from '../action-types'
const voteAction = {
support(payload) {
return {
type: TYPES.VOTE_SUPPORT,
payload
}
},
oppose(payload) {
return {
type: TYPES.VOTE_OPPOSE,
payload
}
}
}
export default voteAction;
// userAction.js
const userAction = {}
export default userAction;
// index.js
import voteAction from "./voteAction";
import userAction from "./userAction";
const action = {
vote: voteAction,
user: userAction
}
export default action;
store->reducers
//userReducer.js
const initinal = {
title: ''
}
export default function userReducer(state = initinal, action) {
state = JSON.parse(JSON.stringify(state))
switch (action.type) {
default:
break;
}
return action
}
// voteReducer.js
import * as TYPES from '../action-types'
const initial = {
title: 'React vs Vue 哪家强?',
suppNum: 0,
oppNum: 0
}
export default function voteReducer(state = initial, action) {
state = JSON.parse(JSON.stringify(state))
switch (action.type) {
case TYPES.VOTE_SUPPORT:
state.suppNum += action.payload;
break;
case TYPES.VOTE_OPPOSE:
state.oppNum += action.payload;
break;
default:
break;
}
return state;
}
// index.js
import { combineReducers } from "redux";
import voteReducer from "./voteReducer";
import userReducer from "./userReducer";
/**
* 合并后的reducer
* state:{
* vote:{
* title: ''
* }
* user:{
* title: ''
* }
* }
*/
const reducer = combineReducers({
vote: voteReducer,
user: userReducer
})
export default reducer;
store->action-types
/** 宏定义 */
// vote模块
export const VOTE_SUPPORT = 'VOTE_SUPPORT';
export const VOTE_OPPOSE = 'VOTE_OPPOSE';
// user模块
store->index.js
import { createStore } from "redux";
import reducer from './reducers'
const store = createStore(reducer);
export default store;
方案三: React-Redux
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './pages/Vote';
import reportWebVitals from './reportWebVitals';
import store from './store';
import { Provider } from 'react-redux'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
vote.jsx
import React, { Component } from 'react'
import VoteHeader from './VoteHeader'
import VoteBody from './VoteBody'
import VoteFooter from './VoteFooter'
export default class Vote extends Component {
render() {
return (
<div>
<VoteHeader/>
<VoteBody/>
<VoteFooter/>
</div>
)
}
}
VoteHeader.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux';
class VoteHeader extends Component {
componentDidMount(){
// const { store } = this.props;
// // 订阅事件
// store.subscribe(()=>this.forceUpdate())
}
render() {
// 解构时后面需要从vote模块获取
console.log(this.props)
const { title, suppNum, oppNum } = this.props;
return (
<div>
<h1>{title}</h1>
<p>NUM:{suppNum + oppNum}</p>
</div>
)
}
}
const mapStateToProps =(state)=>{
console.log(state)// {vote:{...},user:{...}}
return {
...state.vote
}
}
export default connect(mapStateToProps)(VoteHeader)
VoteBody.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
class VoteBody extends Component {
// constructor(props) {
// super(props)
// const { store } = this.props
// console.log(store.getState(),'store')
// const { suppNum, oppNum } = store.getState().vote
// this.state = {
// suppNum,
// oppNum
// }
// }
// componentDidMount() {
// const { store } = this.props
// // 订阅事件
// store.subscribe(() => {
// console.log(store.getState().vote)// {title: 'React vs Vue 哪家强?', suppNum: 0, oppNum: 0 }
// // 解构时后面需要从vote模块获取
// const { suppNum, oppNum } = store.getState().vote
// // 自己做个渲染优化吧
// if (suppNum !== this.state.suppNum) {
// this.setState({
// suppNum: suppNum
// })
// }
// if (oppNum !== this.state.oppNum) {
// this.setState({
// oppNum: oppNum
// })
// }
// })
// }
render() {
const { suppNum, oppNum } = this.props
return (
<div>
<p>suppNum:{suppNum}</p>
<p>oppNum:{oppNum}</p>
</div>
)
}
}
export default connect(state=>state.vote)(VoteBody);
VoteFooter.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import action from '../store/actions'
class VoteFooter extends Component {
render() {
return (
<div>
<button onClick={() => this.handler(0)}>支持</button>
<button onClick={() => this.handler(1)}>反对</button>
</div>
)
}
handler = type => {
const { support, oppose } = this.props
if (type === 0) {
support(10)
return
}
oppose(5)
}
}
// 函数形式
// const mapDispatchToProps = dispatch => {
// return {
// support(payload) {
// dispatch(action.vote.support(payload))
// },
// oppose(payload) {
// dispatch(action.vote.oppose(payload))
// }
// }
// }
// 对象形式
// action.vote= {
// support(payload) {
// return {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// },
// oppose(payload) {
// return {
// type: TYPES.VOTE_OPPOSE,
// payload
// }
// }
// }
export default connect(null, action.vote)(VoteFooter);
store文件夹下的内容和方案二相同,不再赘述。
2.异步解决方法
问题引入,假设现在我们点解投票之后,并不是马上修改值,而是延时1秒之后,才更新值。此时我们就要引入中间件来解决
方案一 rudux-thunk
store/index.js
import { createStore,applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk"
import reducer from './reducers'
const store = createStore(reducer,applyMiddleware(logger,thunk));
export default store;
store/actions/voteAction.js
import * as TYPES from '../action-types'
const voteAction = {
support(payload) {
// setTimeout(() => {
// return {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// }, 1000)
// redux-thunk中间件的使用
return dispatch => {
setTimeout(() => {
dispatch(
{
type: TYPES.VOTE_SUPPORT,
payload
}
)
}, 1000)
}
},
oppose(payload) {
return {
type: TYPES.VOTE_OPPOSE,
payload
}
}
}
export default voteAction;
方案二 rudex-promise
store/index.js
import { createStore,applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk"
import promise from "redux-promise";
import reducer from './reducers'
const store = createStore(reducer,applyMiddleware(logger,thunk,promise));
export default store;
store/actions/voteAction.js
import * as TYPES from '../action-types'
const voteAction = {
support(payload) {
// setTimeout(() => {
// return {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// }, 1000)
// redux-thunk中间件的使用
// return dispatch => {
// setTimeout(() => {
// dispatch(
// {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// )
// }, 1000)
// }
// redux-promise中间件的使用,返回的负荷属性必须用payload,用其他的属性不好用
return {
type: TYPES.VOTE_SUPPORT,
payload: new Promise((resolve, reject) => {
setTimeout(_ => {
resolve(payload)
}, 1000)
})
}
},
oppose(payload) {
return {
type: TYPES.VOTE_OPPOSE,
payload
}
}
}
export default voteAction;
方案三 redux-saga
store/index.js
import { createStore,applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk"
import promise from "redux-promise";
import reducer from './reducers'
// redux-saga
import createSagaMiddleware from "redux-saga";
// 引入根saga
import rootSaga from './saga/rootSaga';
// 创建一个saga中间件
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer,applyMiddleware(logger,thunk,promise,sagaMiddleware));
// 动态执行saga,run函数需要在store创建之后调用
sagaMiddleware.run(rootSaga)
export default store;
saga/rootSage.js
import { call, put,takeEvery } from "redux-saga/effects";
import * as TYPES from '../action-types'
import actions from "../actions";
// const delay = function (ms) {
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve(30)
// }, ms)
// })
// }
export const delay = ms => new Promise((resolve, reject) => setTimeout(() => {
resolve(30)
}, ms))
/**
* 通过action 接收传递的参数
*/
function* asyncSaga(action) {
const data = yield call(delay, 3000);
console.log(action,data)
// yield put({ type: TYPES.VOTE_SUPPORT, payload: data })
yield put(actions.vote.support(action.payload))
}
export default function* rootSaga() {
console.log('启动saga')
yield takeEvery(TYPES.VOTE_ASYNC, asyncSaga)
}
action-types.js
/** 宏定义 */
// vote模块
export const VOTE_SUPPORT = 'VOTE_SUPPORT';
export const VOTE_OPPOSE = 'VOTE_OPPOSE';
// 异步saga
export const VOTE_ASYNC = 'VOTE_ASYNC';
// user模块
voteAction.js
import * as TYPES from '../action-types'
const voteAction = {
support(payload) {
// setTimeout(() => {
// return {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// }, 1000)
// redux-thunk中间件的使用
// return dispatch => {
// setTimeout(() => {
// dispatch(
// {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// )
// }, 1000)
// }
// redux-promise中间件的使用,返回的负荷属性必须用payload,用其他的属性不好用
// return {
// type: TYPES.VOTE_SUPPORT,
// payload: new Promise((resolve, reject) => {
// setTimeout(_ => {
// resolve(payload)
// }, 1000)
// })
// }
return {
type: TYPES.VOTE_SUPPORT,
payload
}
},
oppose(payload) {
return {
type: TYPES.VOTE_OPPOSE,
payload
}
},
asyncSupport(payload) {
return {
type: TYPES.VOTE_ASYNC,
payload
}
}
}
export default voteAction;
VoteFooter.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import action from '../store/actions'
class VoteFooter extends Component {
render() {
const { support, oppose, asyncSupport } = this.props
return (
<div>
<button onClick={() => support(10)}>支持</button>
<button onClick={() => oppose(5)}>反对</button>
// 测试saga 并传递参数
<button onClick={() => asyncSupport(20)}>异步支持</button>
</div>
)
}
handler = type => {
const { support, oppose } = this.props
if (type === 0) {
support(10)
return
}
oppose(5)
}
}
// 函数形式
// const mapDispatchToProps = dispatch => {
// return {
// support(payload) {
// dispatch(action.vote.support(payload))
// },
// oppose(payload) {
// dispatch(action.vote.oppose(payload))
// }
// }
// }
// 对象形式
// action.vote= {
// support(payload) {
// return {
// type: TYPES.VOTE_SUPPORT,
// payload
// }
// },
// oppose(payload) {
// return {
// type: TYPES.VOTE_OPPOSE,
// payload
// }
// }
// }
export default connect(null, action.vote)(VoteFooter);
至此 数据共享的异步和同步方案全部分享完毕,有不妥之处还请见谅。