手挽手带你学React:四档(上)一步一步学会react-redux (自己写个Redux)

栏目: 服务器 · 发布时间: 6年前

内容简介:手挽手带你学React入门四档,用人话教你react-redux,理解redux架构,以及运用在react中。学完这一章,你就可以开始自己的react项目了。之前在思否看到过某个大神的redux搭建 忘记了大神的名字 这里只记得内容了 凭借记忆和当时的学习路线写下本文 隔空感谢本人学习react-redux的时候遇到了很多坎,特别是不理解为什么这么用,这是什么东西,用来做什么。加上各种名词让人无法理解,所以这里我决定用人话,从原理走起,一步一步教大家使用react-redux。

手挽手带你学React入门四档,用人话教你react-redux,理解redux架构,以及运用在react中。学完这一章,你就可以开始自己的react项目了。

之前在思否看到过某个大神的redux搭建 忘记了大神的名字 这里只记得内容了 凭借记忆和当时的学习路线写下本文 隔空感谢

本人学习react-redux的时候遇到了很多坎,特别是不理解为什么这么用,这是什么东西,用来做什么。加上各种名词让人无法理解,所以这里我决定用人话,从原理走起,一步一步教大家使用react-redux。

开始之前

本文开始之前,你需要先了解一些东西,当然我会在文中都一一教给大家。

首先你要会React的基础(这是废话)

对高阶函数有一定的了解

有ES6基础

满足这三项我们开始往下看。

React上下文 context

react官网说,context这个东西你可能永远用不到,但是如果你使用了react-redux那么你还是无意间就使用到了它了。

那么它是个什么东西呢?你可以把它理解为全局的一个可以传递数据的东西,毕竟官方都没给出对于context的定义。

我们直接来看看它是怎么样来让数据可以全局使用的

在使用 context之前 你需要先认识这几个东西

首先需要

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,这里面涉及到了太多高级的东西,大家需要好好消化,不理解的一定要留言提问~~

视频制作中


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Computation and Programming Using Python

Introduction to Computation and Programming Using Python

John V. Guttag / The MIT Press / 2013-7 / USD 25.00

This book introduces students with little or no prior programming experience to the art of computational problem solving using Python and various Python libraries, including PyLab. It provides student......一起来看看 《Introduction to Computation and Programming Using Python》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具