从零开始学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,这里面涉及到了太多高级的东西,你们须要好好消化,不理解的必定要留言提问~~

视频制做中

相关文章
相关标签/搜索