我们一直通过属性来进行组件中的数据传递,这种模式是非常脆弱的。在日常的开发中经常会遇到非父子组件传递的场景。原来的方式是找到共同的父级进行数据交互,这时通信就变得比较麻烦 我们先通过一个简单的例子实现一下redux的工作模式:
let state = {
title:{color:'red',text:'标题'},
content:{color:'green',text:'内容'}
};
function renderContent() {
let content = document.querySelector('.content');
content.innerHTML = state.content.text;
content.style.color = state.content.color;
}
function renderTitle() {
let title = document.querySelector('.title');
title.innerHTML = state.title.text;
title.style.color = state.title.color;
}
function renderApp() {
renderContent();
renderTitle()
}
renderApp();
这里我们可以将renderContent,renderTitle看成两个组件将所需的数据提取到state中统一进行管理。当渲染后我们希望更改状态,封装更改状态的方法(dispatch)
let CHANGE_TITLE_TEXT = 'CHANGE_TITLE_TEXT';
function dispatch(action) {
switch (action.type){
case CHANGE_TITLE_TEXT:
state = {...state,title:{...state.title,text:action.text}};
}
}
setTimeout(()=>{
dispatch({type:CHANGE_TITLE_TEXT,text:'hello'});
renderApp();
},1000);
不要直接更改状态而是使用dispatch方法进行状态的更改,派发一个带有type的属性来进行状态的更改,但是依然无法阻止用户更改状态.
let CHANGE_TITLE_TEXT = 'CHANGE_TITLE_TEXT';
function createStore() {
let state = {
title:{color:'red',text:'标题'},
content:{color:'green',text:'内容'}
};
let getState = () => JSON.parse(JSON.stringify(state)); // 创造一份和状态同样的对象给外界来用
function dispatch(action) {
switch (action.type){
case CHANGE_TITLE_TEXT:
state = {...state,title:{...state.title,text:action.text}};
}
}
return {
dispatch,
getState,
}
}
let store = createStore(); // 拿到createStore中返回的对象
function renderContent() {
let content = document.querySelector('.content');
content.innerHTML = store.getState().content.text;
content.style.color = store.getState().content.color;
}
function renderTitle() {
let title = document.querySelector('.title');
title.innerHTML = store.getState().title.text;
title.style.color = store.getState().title.color;
}
function renderApp() {
renderContent();
renderTitle()
}
renderApp();
setTimeout(()=>{
store.dispatch({type:CHANGE_TITLE_TEXT,text:'hello'});
renderApp();
},1000);
我们将状态放到了createStore函数中,目的是隔离作用域,并且再内部返回深度克隆的对象,这样用户无法再通过外界更改状态。但是状态应该由我们自身来控制,应该是外界传入的,所以要将状态拿出createStore。并且判断的逻辑也应该由我们自己来编写
const CHANGE_TITLE_TEXT = 'CHANGE_TITLE_TEXT';
function createStore(reducer) {
let state;
let getState = () => JSON.parse(JSON.stringify(state));
function dispatch(action) {
state = reducer(state,action);//获取对应的状态覆盖掉store中的状态
}
dispatch({}); // 默认传入空对象获取reducer返回的默认结果
return {
dispatch,
getState,
}
}
let initState = {
title:{color:'red',text:'标题'},
content:{color:'green',text:'内容'}
};
// reducer应该具有默认状态,当更改状态后使用最新的状态
function reducer(state=initState,action) {
switch (action.type){
case CHANGE_TITLE_TEXT:
return {...state,title:{...state.title,text:action.text}};
}
return state
}
此时我们已将需要自己处理的逻辑提取出来,但是我们每次dispatch时都需要自己触发视图的更新,我们希望采用发布订阅来实现。
function createStore(reducer) {
let state;
let listeners = []; // 放置所有订阅的函数
let getState = () => JSON.parse(JSON.stringify(state));
function dispatch(action) {
state = reducer(state,action);
listeners.forEach(item=>item());//每次派发后执行订阅的函数
}
let subscribe = (fn)=>{ //主要用于订阅事件
listeners.push(fn);
return ()=>{ //返回一个移除监听的方法
listeners = listeners.filter(l=>l!==fn);
}
};
dispatch({});
return {
dispatch,
getState,
subscribe
}
}
store.subscribe(renderApp); //通过suscribe订阅派发时需要触发的函数
setTimeout(()=>{
store.dispatch({type:CHANGE_TITLE_TEXT,text:'hello'});
},1000);
此时我们redux中常用的方法已经封装完成!^_^,我们将封装好的逻辑抽离成redux.js
function createStore(reducer) {
let state;
let listeners = []; // 放置所有订阅的函数
let getState = () => JSON.parse(JSON.stringify(state));
function dispatch(action) {
state = reducer(state,action);
listeners.forEach(item=>item());//每次派发后执行订阅的函数
}
let subscribe = (fn)=>{ //主要用于订阅事件
listeners.push(fn);
return ()=>{ //返回一个移除监听的方法
listeners = listeners.filter(l=>l!==fn);
}
};
dispatch({});
return {
dispatch,
getState,
subscribe
}
}
<p id="container"></p>
<button id="add">+</button>
<button id="minus">-</button>
<script src="redux.js"></script>
<script>
const ADD = 'ADD';
const MINUS = 'MINUS';
function reducer(state={number:0},action) {
switch (action.type){
case ADD:
return {number:state.number + action.amount};
case MINUS:
return {number:state.number - action.amount};
}
return state;
}
let store = createStore(reducer);
function render() {
container.innerHTML = store.getState().number
}
render();
store.subscribe(render);
add.addEventListener('click',function () {
store.dispatch({type:ADD,amount:1})
},false);
minus.addEventListener('click',function () {
store.dispatch({type:MINUS,amount:2})
},false)
</script>
由此我们使用了自己的redux库链接了原生js进行使用。
import React,{Component} from 'react'
import ReactDOM,{render} from 'react-dom';
import {createStore} from './redux'
const ADD = 'ADD';
const MINUS = 'MINUS';
function reducer(state={number:0},action) {
switch (action.type){
case ADD:
return {number:state.number + action.amount};
case MINUS:
return {number:state.number - action.amount};
}
return state;
}
let store = createStore(reducer);
class Counter extends React.Component {
constructor(){
super();
this.state = {number:store.getState().number}
}
componentDidMount(){
store.subscribe( () => {
this.setState({number:store.getState().number})
})
}
handleAddClick=()=>{
store.dispatch({type:ADD,amount:1});
};
handleMinusClick=()=>{
store.dispatch({type:MINUS,amount:1});
};
render(){
return <div>
<p>{this.state.number}</p>
<button onClick={this.handleAddClick}>+</button>
<button onClick={this.handleMinusClick}>-</button>
</div>
}
}
ReactDOM.render(<Counter/>,window.root);
这里我们将redux数据映射到了组件自己的状态,并且订阅了setState事件。每次状态更新时都会重新刷新组件