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

53次阅读

共计 12781 个字符,预计需要花费 32 分钟才能阅读完成。

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

正文完
 0