Redux 是一個 JavaScript 應用狀態管理的庫,當專案很複雜的時候,屬性傳遞已經達不到我們預期,可以使用Redux 解決資料傳遞問題,統一狀態管理。換句話說,Redux就是用來處理和管理應用的狀態/資料。
Redux設計思想
Redux是將整個應用狀態儲存到到一個地方,稱為store
裡面儲存一棵狀態樹(state tree)
元件可以派發(dispatch)行為(action)給store,而不是直接通知其它元件
其它元件可以通過訂閱store中的狀態(state)來重新整理自己的檢視
Redux概念解析
Store
Redux的核心是一個store
,就是儲存資料的地方,可以看出是一個容器,整個應用就只能有一個store。Redux提供createStore()
函式來生成store。
import { createStore } from 'redux';
let store = createStore(fn);複製程式碼
上面程式碼中,createStore函式接受另一個函式作為引數,返回新生成的Store物件。
State
store 某個節點對應的資料集合就是state。state
是被託管的資料,也就是每次觸發監聽事件,我們要操作的資料。可以通過store.getState()
獲得。
Redux 規定,一個state對應一個View。State相同,則View相同。
let store = createStore(fn);
let state = store.getState();複製程式碼
Action
State 的變化,會導致 View 的變化。但是,使用者接觸不到 State,只能接觸到 View。所以,State 的變化必須是 View 導致的。Action 就是 View 發出的通知,表示 State 應該要發生變化了。
Action 是一個物件。其中的type
屬性是必須的,表示 Action 的名稱。其他屬性可以自由設定。
{ type: types.ADD_TODO , text: '讀書' }複製程式碼
Actor Creator
action creator 顧名思義就是用來建立 action 的,action creator 只簡單的返回 action。
const ADD_TODO = 'ADD_TODO';
let actions = {
addTodo(todo){
return { type: ADD_TODO, todo}
}
}複製程式碼
store.dispath(action)
store.dispatch()是 View 發出 Action 的唯一方法。store.dispatch接受一個 Action 物件作為引數,將它傳送出去
let store=createStore(reducer);
store.dispatch({type:'ADD_TODO',text:'讀書'});複製程式碼
結合 Action Creator,這段程式碼可以改寫如下。
store.dispatch(actions.addTodo(e.target.value))複製程式碼
Redux 核心API
createStore
使用方法
let store=createStore(reducer);
reducer
在Redux中,負責響應
action
並修改資料的角色就是reducer
。 reducer要有兩個引數 ,要根據老的狀態和新傳遞的動作算出新的狀態。reducer
本質上是一個純函式,每次需要返回一個新的狀態物件。//以下為reducer的格式 const todo = (state = initialState, action) => { switch(action.type) { case 'XXX': return //具體的業務邏輯; case 'XXX': return //具體的業務邏輯; default: return state; } }複製程式碼
getState()
將狀態放到了容器中外部無法在進行更改了,使用獲取
store
中的狀態。dispatch(action)
subscribe(listener)
combineReducers
合併reducer,把他們合併成一個
key是新狀態的名稱空間,值是reducer,執行後會返回一個新的reducer。
let reducer = combineReducers({
c: counter,
t: todo
});複製程式碼
context
react提供一個context API,可以解決跨元件的資料傳遞。16.3版本以前的context和現在最新版context用法有區別。在16.3官方不推薦使用,如果某個元件shouldComponentUpdate返回了false後面的元件就不會更新了
contextAPI 新的方法非常簡便。
import React from 'react';
import {render} from 'react-dom';
// 建立一個上下文,有兩個屬性 一個叫Provider 還有個叫Consume
// createContext中的物件是預設引數
let { Consumer,Provider} = React.createContext();
// context 可以建立多個 這時候就不要解構了,不同的context是不能互動的
class Title extends React.Component{
render(){
// 子類通過Consumer進行消費 內部必須是一個函式 函式的引數是Provider的value屬性
return <Consumer>
{({s,h})=>{
return <div style={s} onClick={()=>{
h('red');
}}>hello</div>
}}
</Consumer>
}
}
class Head extends React.Component{
render() {
return <div>
<Title></Title>
</div>
}
}
// Provider使用在父元件上
class App extends React.Component{
constructor(){
super();
this.state = {color:'green'}
}
handleClick = (newColor) =>{
this.setState({ color: newColor})
}
render(){
return <Provider value={{ s: this.state,h:this.handleClick}}>
<Head></Head>
</Provider>
}
}
render(<App></App>,window.root)複製程式碼
實現簡單的Redux
瞭解基本概念後就來寫一個實現簡單功能的"redux"吧!實現把內容渲染到頁面上
1.渲染狀態
//資料來源
let appState={
title: {color: 'red',text: '標題'},
content:{color:'green',text:'內容'}
}
// 渲染標題
function renderTitle(title) {
let titleEle=document.querySelector('#title');
titleEle.innerHTML=title.text;
titleEle.style.color=title.color;
}
// 渲染內容
function renderContent(content) {
let contentEle=document.querySelector('#content');
contentEle.innerHTML=content.text;
contentEle.style.color=content.color;
}
// 執行渲染的方法
function render(appState) {
renderTitle(appState.title);
renderContent(appState.content);
}
render(appState);複製程式碼
2.提高資料修改的門檻
狀態不應該是全域性的,也不應該哪個方法裡直接可以更改(操作危險)
一旦資料可以任意修改,所有對共享狀態的操作都是不可預料的
模組之間需要共享資料和資料可能被任意修改導致不可預料的結果之間有矛盾
所以提供一個修改狀態的
dispatch
方法,不要去直接更改狀態,對資料的操作修改必須通過這個方法
let appState={
title: {color: 'red',text: '標題'},
content:{color:'green',text:'內容'}
}
function renderTitle(title) {
let titleEle=document.querySelector('#title');
titleEle.innerHTML=title.text;
titleEle.style.color=title.color;
}
function renderContent(content) {
let contentEle=document.querySelector('#content');
contentEle.innerHTML=content.text;
contentEle.style.color=content.color;
}
function render(appState) {
renderTitle(appState.title);
renderContent(appState.content);
}
//先定義好要做那些事情(常量) 也叫巨集
const UPDATE_TITLE_COLOR = 'UPDATE_TITLE_COLOR';
const UPDATE_CONTENT_CONTENT = 'UPDATE_CONTENT_CONTENT';
// 派發的方法,用來更改狀態
// 派發時應該將修改的動作action提交過來,是個物件,物件裡的type屬性是固定必須的。
function dispatch(action) {
switch (action.type) {
case UPDATE_TITLE_COLOR:
appState.title.color=action.color;
break;
case UPDATE_CONTENT_CONTENT:
appState.content.text=action.text;
break;
default:
break;
}
}
dispatch({type:UPDATE_TITLE_COLOR,color:'purple'});
dispatch({type:UPDATE_CONTENT_CONTENT,text:'新標題'});
render(appState);複製程式碼
3.分裝倉庫
把狀態放進一個容器裡,將定義狀態和規則的部分抽離到容器外面
function renderTitle(title) {
let titleEle=document.querySelector('#title');
titleEle.innerHTML=title.text;
titleEle.style.color=title.color;
}
function renderContent(content) {
let contentEle=document.querySelector('#content');
contentEle.innerHTML=content.text;
contentEle.style.color=content.color;
}
function render(appState) {
renderTitle(appState.title);
renderContent(appState.content);
}
// 容器
function createStore(reducer) {
let state;
// 讓外面可以獲取狀態
function getState() {
return state;
}
function dispatch(action) {
state=reducer(state,action);
}
dispatch({});
// 將方法暴露給外面使用,將狀態放到了容器中外部無法在進行更改了
return { getState , dispatch }
}
// 容器一般會封裝成庫
// 將定義狀態和規則的部分抽離到容器外面,再傳進去
let initState={
title: {color: 'red',text: '標題'},
content:{color:'green',text:'內容'}
}
const UPDATE_TITLE_COLOR = 'UPDATE_TITLE_COLOR';
const UPDATE_CONTENT_CONTENT = 'UPDATE_CONTENT_CONTENT';
// 使用者自己定義的規則,我們叫它reducer,也就是所謂的管理員
// reducer要有兩個引數,要根據老的狀態和新傳遞的動作算出新的狀態
// 如果想獲取預設狀態,有一種方式,就是呼叫reducer,讓每一個規則都不匹配將預設值返回
// 在reducer中,reducer是一個純函式,每次需要返回一個新的狀態,只承擔計算 State 的功能
let reducer=function (state=initState,action) {
switch (action.type) {
case UPDATE_TITLE_COLOR:
return {...state,title: {...state.title,color:action.color}};
case UPDATE_CONTENT_CONTENT:
return {...state,content: {...state.content,text:action.text}};
break;
default:
return state;
}
}
let store=createStore(reducer);
render(store.getState());
setTimeout(function () {
store.dispatch({type:UPDATE_TITLE_COLOR,color:'purple'});
store.dispatch({type:UPDATE_CONTENT_CONTENT,text:'新標題'});
render(store.getState());
},2000);複製程式碼
4.監控資料變化
function renderTitle(title) {
let titleEle=document.querySelector('#title');
titleEle.innerHTML=title.text;
titleEle.style.color=title.color;
}
function renderContent(content) {
let contentEle=document.querySelector('#content');
contentEle.innerHTML=content.text;
contentEle.style.color=content.color;
}
function render() {
renderTitle(store.getState().title);
renderContent(store.getState().content);
}
function createStore(reducer) {
let state;
let listeners=[];
function getState() {
return state;
}
// 釋出訂閱模式,先將render方法訂閱好,每次dispatch時都呼叫訂閱好的方法
function dispatch(action) { // 釋出
state=reducer(state,action);
listeners.forEach(l=>l());
}
function subscribe(listener) { // 訂閱
listeners.push(listener);
return () => {
// 再次呼叫時 移除監聽函式
listeners = listeners.filter(item => item!=listener);
console.log(listeners);
}
}
dispatch({});
return { getState,dispatch,subscribe }
}
let initState={
title: {color: 'red',text: '標題'},
content:{color:'green',text:'內容'}
}
let reducer=function (state=initState,action) {
switch (action.type) {
case UPDATE_TITLE_COLOR:
return {...state,title: {...state.title,color:action.color}};
case UPDATE_CONTENT_CONTENT:
return {...state,content: {...state.content,text:action.text}};
break;
default:
return state;
}
}
let store=createStore(reducer);
render();
let unsubscribe = store.subscribe(render);
setTimeout(function () {
store.dispatch({type:'UPDATE_TITLE_COLOR',color:'purple'});
unsubscribe();
store.dispatch({type:'UPDATE_CONTENT_CONTENT',text:'新標題'});
},2000);複製程式碼