從零開始學React:四檔(上)一步一步學會react-redux (本身寫個Redux)

手挽手帶你學React入門四檔,用人話教你react-redux,理解redux架構,以及運用在react中。學完這一章,你就能夠開始本身的react項目了。javascript

以前在思否看到過某個大神的redux搭建 忘記了大神的名字 這裏只記得內容了 憑藉記憶和當時的學習路線寫下本文 隔空感謝html

視頻教程

本人學習react-redux的時候遇到了不少坎,特別是不理解爲何這麼用,這是什麼東西,用來作什麼。加上各類名詞讓人沒法理解,因此這裏我決定用人話,從原理走起,一步一步教你們使用react-redux。java

開始以前

本文開始以前,你須要先了解一些東西,固然我會在文中都一一教給你們。react

首先你要會React的基礎(這是廢話) 對高階函數有必定的瞭解 有ES6基礎 知足這三項咱們開始往下看。redux

React上下文 context

react官網說,context這個東西你可能永遠用不到,可是若是你使用了react-redux那麼你仍是無心間就使用到了它了。 那麼它是個什麼東西呢?你能夠把它理解爲全局的一個能夠傳遞數據的東西,畢竟官方都沒給出對於context的定義。緩存

咱們直接來看看它是怎麼樣來讓數據能夠全局使用的性能優化

在使用 context以前 你須要先認識這幾個東西架構

首先須要dom

import PropTypes from 'prop-types';函數

prop-types這個東西是一個幫你作類型檢測的 因此咱們直接就使用好了

接下來是 childContextTypes 這個屬性 它是一個對象,裏面規定咱們要經過context來傳遞給下面的屬性名和類型 它一般在父組件中

而後是 getChildContext(){} 這是個規定好的方法 內部retrun一個對象 用來初始化 context的數據

最後是 contextTypes 這個屬性 它也是一個對象,裏面規定咱們要接收context來傳遞給下面的屬性名和類型 它一般在子組件中

好了 瞭解了的話 咱們開始寫第一個 context了

// App.js
import React,{Component} from 'react'
import PropTypes from 'prop-types'  //引入

export default class App extends Component {
    static childContextTypes = {  //聲明要經過context傳遞的東西
        propA: PropTypes.string,
        methodA: PropTypes.func
      }

        getChildContext () {  //初始化context
            return {
            propA: 'propA',
            methodA: () => 'methodA'
        }
    }

    constructor(){
        super()

        this.state={
          
        }
    }
    componentWillMount(){
        // console.log(hashHistory)
    }
    render() {
        return (
            <div> <Children /> </div>
        )
    }
  
}

// 爲了展現效果定義子組件一

class Children extends Component{
    constructor(){
        super()
        this.state={
            
        }
    }
    static contextTypes = {   //規定要接收的東西
        propA: PropTypes.string
      }
 
    render(){
        console.log(this.context.methodA)  // 由於沒有規定 因此如今是 undefined
        return(
            <div> <ChildrenTwo /> <h1>{this.context.propA} </h1> {/* 展現propA */} </div>
        )
    }
}

// 爲了展現效果定義子組件二 ChildrenTwo 是 Children的子組件 可是卻經過context拿到了App組件拿過來的值 (越級傳遞)

class ChildrenTwo extends Component{
    static contextTypes={
        methodA: PropTypes.func
    }
    constructor(){
        super()
        this.state={
        
        }
    }
    render(){
        return(
            <div> <h1>{this.context.methodA()}</h1> {/* 展現methodA */} </div>
        )
    }
}


複製代碼

通俗一點來講 一個組件 經過 getChildContext方法來返回一個對象 這就是咱們的context 通過了 childContextTypes 聲明後 它的下層組件均可以經過 contextTypes 聲明。而後經過this.context獲取到內容而且使用了。

好了 context這裏就講完了,你們把它放到你大腦的後臺裏運行着,可能在這裏你一頭霧水,講這個幹毛。好的,咱們接下來實現一個redux架構!

從零開始Redux

咱們建立一個HTML文件,就叫redux.html 全部東西咱們寫在這一個html裏面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="myHead"></div>
    <div id="myBody"></div>
    <!-- 咱們在這裏定義兩個基礎dom -->
</body>
<script> const state={ myHead:{ color:"red", context:"我是腦殼" }, myBody:{ color:"blue", context:"我是身體" } } // 模擬狀態  // 而後咱們聲明三個渲染函數 function renderMyHead(myHead){ var DOM = document.getElementById('myHead') DOM.innerHTML = myHead.context DOM.style.color = myHead.color } function renderMyBody(myBody){ var DOM = document.getElementById('myBody') DOM.innerHTML = myBody.context DOM.style.color = myBody.color } function renderApp(state){ renderMyHead(state.myHead) renderMyBody(state.myBody) } renderApp(state) </script>
</html>

複製代碼

上面的代碼,經過函數渲染把狀態內的東西渲染到了視圖中,可是,這裏的狀態是暴露在外面的,任何一個地方均可以修改這個數據。這樣就不存在穩定性可言了,咱們想象一下,若是咱們如今規定,你主動修改的state讓程序直接無視掉,只有你經過我給你的方法去修改,我纔會承認這個狀態。所以 dispatch就出現了,這是修改數據惟一的地方。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="myHead"></div>
    <div id="myBody"></div>
    <!-- 咱們在這裏定義兩個基礎dom -->
    <!-- 定義一個按鈕用來觸發dispatch看效果 -->
    <button onclick='change()'>調用dispatch</button>
</body>
<script> const state={ myHead:{ color:"red", context:"我是腦殼" }, myBody:{ color:"blue", context:"我是身體" } } // 模擬狀態  // 而後咱們聲明三個渲染函數 function renderMyHead(myHead){ var DOM = document.getElementById('myHead') DOM.innerHTML = myHead.context DOM.style.color = myHead.color } function renderMyBody(myBody){ var DOM = document.getElementById('myBody') DOM.innerHTML = myBody.context DOM.style.color = myBody.color } function renderApp(state){ renderMyHead(state.myHead) renderMyBody(state.myBody) } // 咱們在這裏聲明一個dispatch函數 function dispatch(action){ switch (action.type){ case 'UPDATE_HEAD_COLOR': state.myHead.color=action.color break; case 'UPDATE_HEAD_CONTEXT': state.myHead.context=action.context break; default: break; } } function change(){ // 寫一個方法來觸發dispatch dispatch({type:'UPDATE_HEAD_COLOR',color:'black'}) dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) // 更新事後渲染 renderApp(state) } renderApp(state) </script>
</html>

複製代碼

如今 你能夠經過dispatch來修改state內容了,而且必需要按照它的聲明方式,和修改方式有規律地修改了。

是時候建立一個store了

咱們如今有了數據,而且能夠修改數據了,咱們是否是能夠建立咱們的倉庫了?它的名字叫作 store ,固然,若是咱們手動把這些東西塞進去,那就顯得太low了,使用函數做爲一個工廠,幫咱們生成這個那是極其舒坦的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="myHead"></div>
    <div id="myBody"></div>
    <!-- 咱們在這裏定義兩個基礎dom -->
    <!-- 定義一個按鈕用來觸發dispatch看效果 -->
    <button onclick='change()'>調用dispatch</button>
</body>
<script> const state={ myHead:{ color:"red", context:"我是腦殼" }, myBody:{ color:"blue", context:"我是身體" } } // 模擬狀態  // 而後咱們聲明三個渲染函數 function renderMyHead(myHead){ var DOM = document.getElementById('myHead') DOM.innerHTML = myHead.context DOM.style.color = myHead.color } function renderMyBody(myBody){ var DOM = document.getElementById('myBody') DOM.innerHTML = myBody.context DOM.style.color = myBody.color } function renderApp(state){ renderMyHead(state.myHead) renderMyBody(state.myBody) } // 咱們在這裏聲明一個dispatch函數 function stateChanger(state,action){ //封裝事後咱們須要告訴它 state來自哪裏 switch (action.type){ case 'UPDATE_HEAD_COLOR': state.myHead.color=action.color break; case 'UPDATE_HEAD_CONTEXT': state.myHead.context=action.context break; default: break; } } function creatStore(state,stateChanger){ //這裏咱們建立一個函數 第一個參數是咱們要用的狀態倉 第二個是咱們本身作的dispatch  const getState = () => state const dispatch = (action)=> stateChanger(state,action) //state就是咱們放進來的狀態 action是咱們調用時候傳進來 return{getState,dispatch} } const store = creatStore(state,stateChanger) // 這裏咱們生成了store  renderApp(store.getState()) // 渲染 function change(){ store.dispatch({type:'UPDATE_HEAD_COLOR',color:'black'}) //改變state數值 store.dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) //改變state數值 renderApp(store.getState()) //渲染 } </script>
</html>

複製代碼

到這裏咱們看到了一點Redux的雛形了,可是咱們每次都要手動調用渲染,這是否是就很是地不爽。接下來咱們要監聽數據變化,讓它本身渲染數據。那麼這個監聽在哪裏呢?沒錯store裏面

設置數據監聽

你們可能想到 咱們若是把渲染數據加入到dispatch裏面不就行了嗎?沒錯,不過咱們確實要在dispatch裏面作文章。

function creatStore(state,stateChanger){  //這裏咱們建立一個函數 第一個參數是咱們要用的狀態倉 第二個是咱們本身作的dispatch 
        const getState = () => state
        const dispatch = (action)=> {
              stateChanger(state,action) 
            // 這裏咱們改變了狀態 而後咱們須要刷新視圖
              renderApp(state)
        }  //state就是咱們放進來的狀態 action是咱們調用時候傳進來
        return{getState,dispatch}
    }

    const store = creatStore(state,dispatch) // 這裏咱們生成了store 

    renderApp(store.getState())  // 渲染
    store.dispatch({type:'UPDATE_HEAD_COLOR',color:'black'})  //改變state數值
    store.dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) //改變state數值
    // 如今咱們能夠監聽數據變化了
複製代碼

可是這裏咱們遇到一個問題,這個creatStore只適用於咱們當前的項目啊,不可以通用啊。這該怎麼辦呢? 其實簡單 咱們動態傳入渲染的方法不就行了嗎 因而咱們把代碼改爲這樣

function creatStore(state,stateChanger){  //這裏咱們建立一個函數 第一個參數是咱們要用的狀態倉 第二個是咱們本身作的dispatch 
        const getState = () => state
        const listenerList = []
        const subscribe = (listener) => listenerList.push(listener)
        const dispatch = (action)=> {
              stateChanger(state,action) 
            // 這裏咱們改變了狀態 而後咱們須要刷新視圖
              listenerList.map(item=>item())
        }  //state就是咱們放進來的狀態 action是咱們調用時候傳進來

        return{getState,dispatch,subscribe}
    }

    const store = creatStore(state,stateChanger) // 這裏咱們生成了store 
    store.subscribe(()=>renderApp(store.getState()))
    renderApp(store.getState())  // 渲染
    store.dispatch({type:'UPDATE_HEAD_COLOR',color:'black'})  //改變state數值
    store.dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) //改變state數值
    // 如今咱們能夠動態加入監聽了
複製代碼

性能優化

寫到這裏 問題又出現了,每次咱們改動一個數據 或者數據沒有改動 只要是調用了 dispatch 咱們就會觸發所有的刷新 咱們加上console.log看一下

// 而後咱們聲明三個渲染函數 function renderMyHead(myHead){
    function renderMyHead(myHead){
        console.log("渲染了Head")
        var DOM = document.getElementById('myHead')
        DOM.innerHTML = myHead.context
        DOM.style.color = myHead.color
    }

    function renderMyBody(myBody){
        console.log("渲染了Body")
        var DOM = document.getElementById('myBody')
        DOM.innerHTML = myBody.context
        DOM.style.color = myBody.color
    }

    function renderApp(state){
        console.log("渲染了App")

        renderMyHead(state.myHead)
        renderMyBody(state.myBody)
    }

複製代碼

加上這些console之後 你會發現 咱們只改變了head 可是 body也被從新渲染了 這就大大浪費了性能啊 咱們怎麼辦呢?沒錯 渲染以前檢測一下數據變沒變

不過咱們先拋出一個問題

function renderMyHead(newMyHead,oldMyHead={}){
        if(newMyHead==oldMyHead){
            return 
        }
        console.log("渲染了Head")
        var DOM = document.getElementById('myHead')
        DOM.innerHTML = newMyHead.context
        DOM.style.color = newMyHead.color
    }
    function renderMyBody(newMyBody,oldMyBody={}){
        if(newMyBody===oldMyBody){
            return
        }
        console.log("渲染了Body")
        var DOM = document.getElementById('myBody')
        DOM.innerHTML = newMyBody.context
        DOM.style.color = newMyBody.color
    }

    function renderApp (newState, oldState = {}) {
        if (newState === oldState) {
            return
        }
        renderMyHead(newState.myHead, oldState.myHead)
        renderContent(newState.myBody, oldState.myBody)
    }

    const store = creatStore(state,dispatch) // 這裏咱們生成了store 
    let oldState = store.getState()

    store.subscribe(()=>{
        const newState = store.getState() // 數據可能變化,獲取新的 state
        renderApp(newState,oldState)  //把新舊數據傳禁區
        oldState = newState //記錄數據
    })
    renderApp(store.getState())  // 渲染
    store.dispatch({type:'UPDATE_HEAD_COLOR',color:'black'})  //改變state數值
    store.dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) //改變state數值

複製代碼

好的 到這裏 問題來了,咱們寫這個有用嗎?

答案顯然易見 咱們作這個等同於

let obj = {cc:1}
    let oldObj = obj
    obj.cc = 3 
    obj===oldObj  // true
複製代碼

他們都指向了同一個地址呀 這有什麼做用

因此咱們如今要作的就是須要對 stateChanger內部的state返回模式進行改動,咱們再也不返回值,而是返回對象,當有對象返回的時候,咱們的newState確定就不等於oldState了,說到就作,嘗試一下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="myHead"></div>
    <div id="myBody"></div>
    <!-- 咱們在這裏定義兩個基礎dom -->
</body>
<script> const state={ myHead:{ color:"red", context:"我是腦殼" }, myBody:{ color:"blue", context:"我是身體" } } // 模擬狀態  // 而後咱們聲明三個渲染函數 function renderMyHead(newMyHead,oldMyHead={}){ if(newMyHead===oldMyHead){ //當數據相同的時候 不渲染 return } console.log("渲染了Head") var DOM = document.getElementById('myHead') DOM.innerHTML = newMyHead.context DOM.style.color = newMyHead.color } function renderMyBody(newMyBody,oldMyBody={}){ if(newMyBody===oldMyBody){ //當數據相同的時候 不渲染 return } console.log("渲染了Body") var DOM = document.getElementById('myBody') DOM.innerHTML = newMyBody.context DOM.style.color = newMyBody.color } function renderApp(newState,oldState={}){ console.log('來了',newState,oldState) if(newState===oldState){ //當數據相同的時候 不渲染 return } console.log("渲染了App") renderMyHead(newState.myHead,oldState.myHead) renderMyBody(newState.myBody,oldState.myBody) } // 咱們在這裏聲明一個dispatch函數 function stateChanger(state,action){ switch (action.type){ case 'UPDATE_HEAD_COLOR': return{ //這裏咱們使用ES6 再也不去修改原來的state 而是 返回一個新的state 咱們 creatStore裏面的 dispatch方法也要跟着改動 ...state, myHead:{ ...state.myHead, color:action.color } } break; case 'UPDATE_HEAD_CONTEXT': return{ ...state, myHead:{ ...state.myHead, context:action.context } } break; default: return{...state} break; } } function creatStore(state,stateChanger){ //這裏咱們建立一個函數 第一個參數是咱們要用的狀態倉 第二個是咱們本身作的dispatch  const getState = () => state const listenerList = [] const subscribe = (listener) => listenerList.push(listener) const dispatch = (action)=> { state = stateChanger(state,action) //這裏咱們直接覆蓋原來是state  // 這裏咱們改變了狀態 而後咱們須要刷新視圖 listenerList.map(item=>item()) } return{getState,dispatch,subscribe} } const store = creatStore(state,stateChanger) // 這裏咱們生成了store  let oldStore = store.getState() //緩存舊數據 store.subscribe(()=>{ let newState = store.getState() //得到新數據 renderApp(newState,oldStore) //調用比較渲染 oldStore = newState //數據緩存 }) renderApp(store.getState()) store.dispatch({type:'UPDATE_HEAD_COLOR',color:'black'}) //改變state數值 store.dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) //改變state數值 // 通過咱們一番改進 咱們再也不去調用Body的渲染了 </script>
</html>

複製代碼

到這裏咱們已經搭建了本身的一個簡單的redux了,咱們繼續往react-redux靠近

reducer

咱們上面寫 creatStore的時候 傳入了兩個參數 state和 stateChanger 咱們是否是能夠把這兩個也合併到一塊兒呢?沒問題 合併完了就是咱們react-redux的reducer

// 咱們就從stateChanger這個函數開始改
    function stateChanger(state,action){
        // 這裏咱們多加一個判斷 是否有state 若是沒有 咱們就return一個 
        if(!state){
            return{
                myHead:{
                    color:"red",
                    context:"我是腦殼"
                },
                myBody:{
                    color:"blue",
                    context:"我是身體"
                }
            }
        }
        switch (action.type){
            case 'UPDATE_HEAD_COLOR':
                return{   //這裏咱們使用ES6 再也不去修改原來的state 而是 返回一個新的state 咱們 creatStore裏面的 dispatch方法也要跟着改動
                    ...state,
                    myHead:{
                        ...state.myHead,
                        color:action.color
                    }
                }
            break;
            case 'UPDATE_HEAD_CONTEXT':
               return{
                    ...state,
                    myHead:{
                        ...state.myHead,
                        context:action.context
                    }
               }
            break;
            default:
                return{...state}
            break;
        }
    }

    function creatStore(stateChanger){  //如今咱們不須要傳入state了 只須要傳入stateChanger 就行了 由於咱們能夠拿到它
        let state = null
        const getState = () => state
        const listenerList = []
        const subscribe = (listener) => listenerList.push(listener)
        const dispatch = (action)=> {
             state = stateChanger(state,action)   //這裏咱們直接覆蓋原來是state 
            // 這裏咱們改變了狀態 而後咱們須要刷新視圖
              listenerList.map(item=>item())
        }
        dispatch({}) // 這裏初始化 state
        // 咱們一切都聲明完成 只須要調用一次 dispatch({}) 由於咱們的state是null 因此 執行了 state = stateChanger(state,action) 從而獲得了咱們stateChanger內部設置的state了
        return{getState,dispatch,subscribe}
    }

        const store = creatStore(stateChanger) // 這裏咱們生成了store 而且不用傳入state了 只要把咱們寫好的 stateChanger放進去就行了 
        // 這個 stateChanger 官方稱之爲 reducer
            let oldStore = store.getState()   //緩存舊數據
            store.subscribe(()=>{
                let newState = store.getState()  //得到新數據
                renderApp(newState,oldStore)   //調用比較渲染
                oldStore = newState   //數據緩存
            }) 
            renderApp(store.getState())
            store.dispatch({type:'UPDATE_HEAD_COLOR',color:'black'})  //改變state數值
            store.dispatch({type:'UPDATE_HEAD_CONTEXT',context:'我變了'}) //改變state數值
            // 通過咱們一番改進 咱們再也不去調用Body的渲染了

複製代碼

到這裏 你會忽然發現,本身居然動手實現了一套redux!咱們要和react結合起來 還須要一個過程。

總結

在咱們四檔上篇裏面,從零開始搭建了一個本身的redux,這裏面涉及到了太多高級的東西,你們須要好好消化,不理解的必定要留言提問~~

視頻製做中

相關文章
相關標籤/搜索