我们用「原生JS + Redux」来完成一个「Counter」的小需求。
在上一节中(https://www.jianshu.com/p/ff0bfe9a0981),我们已经用过「Redux」改写过需求。今天我们再来剖析下「Redux」。
JS代码:
let createStore = Redux.createStore;
let store = createStore(stateChanger);
// stateChanger函数里面,集合了所有「数据修改」操作
function stateChanger(state, action) {
if(typeof state === "undefined") {
state = 0;
}
switch(action.type) {
case 'ADD':
return state = state + action.payload;
case 'MINUS':
return state = state - action.payload;
default:
return state;
}
}
function render() {
let app = document.querySelector('#root');
app.innerHTML = `
${store.getState()}
`;
}
function add() {
// dispatch相当于发送一个action,然后根据「stateChanger」
// 里面的类型,进行数据更新
store.dispatch({type: 'ADD', payload: 1});
}
function minus() {
store.dispatch({type: 'MINUS', payload: 1});
}
function addOdd() {
if(store.getState() % 2 === 1) {
store.dispatch({type: 'ADD', payload: 2});
}
}
function addAsync() {
setTimeout(() => {
store.dispatch({type: 'ADD', payload: 2});
}, 1000)
}
render();
// 监听store,如果store发生变化,则执行回调函数。
store.subscribe(() => {
render();
});
总结下上述代码,「Redux」会多几个术语,分别为「store,stateChanger,dispatch,subscriber」。
- store,顾名思义,就是所有数据的集合。
- stateChanger,一般也称为「reducer」,就是所有「更新数据的行为」的集合。
- dispatch,可以理解为发送action,然后根据「stateChanger」里面的action type来更新「store」里面的数据。
- payload, 就是action里面的data值。
- subscriber,监听「store」,一旦「store」里面的数据有变动,就会执行回调函数,一般这个回调函数就是render函数,来重新渲染需要更新的页面。
接下来我们进入「第二阶段」,运用「React」和「Redux」来再次实现上述需求。
我们需要装一个包「create-react-app」,可以为我们搭建好一个React环境。同时记得装「Redux」。都安装好后,项目结构如下:
我们只操作「App.js」和「index.js」文件。
App.js
import React, { Component } from 'react';
class App extends Component {
add() {
this.props.store.dispatch({type: 'ADD', payload: 1});
}
minus() {
this.props.store.dispatch({type: 'MINUS', payload: 1});
}
addOdd() {
if(this.props.store.getState() % 2 === 1) {
this.props.store.dispatch({type: 'ADD', payload: 2});
}
}
addAsync() {
setTimeout(() => {
this.props.store.dispatch({type: 'ADD', payload: 1});
}, 1000);
}
render() {
return (
{this.props.store.getState()}
);
}
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {createStore} from 'redux';
let store = createStore(stateChanger);
function stateChanger(state, action) {
if(typeof state === "undefined") {
state = 0;
}
switch(action.type) {
case 'ADD':
return state = state + action.payload;
case 'MINUS':
return state = state - action.payload;
default:
return state;
}
}
render();
store.subscribe(() => {
render();
})
function render() {
ReactDOM.render(, document.getElementById('root'));
}
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
我们换了「React+Redux」方案后,可以打开「开发者工具」,看到最显著的变化就是,这样只会局部更新。更新需要改变的节点。
但是也产生一个问题
我们进入第三阶段,引用「React-Redux」。同样只是修改「App.js」和「index.js」。
App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
class App extends Component {
addOdd() {
if(this.props.number % 2 === 1) {
this.props.addOdd()
}
}
addAsync() {
setTimeout(() => {
this.props.addAsync();
}, 1000)
}
render() {
return (
{this.props.number}
);
}
}
const mapStateToProps = (state) => {
return {
number: state.number
}
};
// 注意「mapDispatchToProps」是一个对象
const mapDispatchToProps = {
add: () => {
return {
type: 'ADD',
payload: 1
}
},
minus: () => {
return {
type: 'MINUS',
payload: 1
}
},
addOdd: () => {
return {
type: 'ADD',
payload: 2
}
},
addAsync: () => {
return {
type: 'ADD',
payload: 2
}
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {createStore} from 'redux';
import { Provider } from 'react-redux';
let store = createStore(stateChanger);
function stateChanger(state, action) {
if(typeof state === "undefined") {
return {
number: 0
}
}
switch(action.type) {
case 'ADD':
return {
number: state.number + action.payload
};
case 'MINUS':
return {
number: state.number - action.payload
};
default:
return state;
}
}
render();
store.subscribe(() => {
render();
})
function render() {
ReactDOM.render(
,
document.getElementById('root'));
}
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
通过一个「Provider」组件把「App」组件包裹住,然后把「store」放在「Provider」组件上。在「App」组件中,最后使用「connect」函数,第一个参数接受「mapStateToProps」和「mapDispatchToProps」。
「mapStateToProps」的作用是是接受父组件传来的「store」并只获取其中部分属性,获得的部分属性供当前组件使用,当前组件可以通过「this.props」调用。
「mapDispatchToProps」是一个「对象」,里面的每个属性就是action,当前组件可以通过「this.props」统一调用。
我们可以log看看「this.props」。
大概流程就是,「App.js」中点击按钮,通过「mapDispatchToProps」,然后「index.js」中的「stateChanger」函数来更新「store」,「store」有更新就被「subscriber」监听到,然后重新渲染,固然传入「Provider」的「store」有改变,然后「App.js」中的「mapStateToProps」获取整个「store」的「部分属性」,供当前组件使用,可以使用「this.props」获取「属性值」和「方法」。