开场白
19年的第一篇文章,虽然18年也没有分享多少,但是19年开始,我觉得要好好学习,好好努力。当然新的一年伊始,祝大家在19年平安、幸福,还有发发发。
导语
redux解决的核心问题是父子兄弟等组件件传值很麻烦的问题,于是有了一个"通讯班"--redux,这个通讯班可以帮我们把组件之间的状态整合到一起,然后修改也统一修改。我们觉得很nice。上一篇文章我简单的跟大家解读了redux的工作原理。但是,修改完成以后,把修改的东西再通知下去我们又会觉得是一个麻烦,我们也希望能有这样一个通讯班来帮我们把命令传达下去。为了解决这个问题,react-redux义无反顾的出现了。这样相对来说就比较完美了。那今天我们就会想把整个工作流都自己来简单实现了,也便有了接下来的故事。redux、react-redux兄弟同心,齐力传值。
redux三板斧
redux三板斧,store、action,reducer。 之前简单了做了一个redux。今天,把上次的代码优化下,做个精致的redux,但是其实里面的东西都是差不多的。在这一部分的分享我也不会做太详细的讲解,如果过程中有疑问可以看下上篇文章,或者看完后不懂大家可以留言互相交流。 createStore创建的对象拥有4个API,他们分管不同的职能。
export const createStore = (state,storeChange) => {
const listeners = [];
let store = state || {};
const subscribe = (listener) => {
listeners.push(listener)
}
const dispatch = (action) => {
const newStore = storeChange(store,action);
store = newStore;
listeners.forEach((item) => item())
}
const getStore = () => {
return store;
}
return {store,dispatch,subscribe,getStore}
}
复制代码
subcribe使用订阅发布者模式。组件订阅了,中央有改变的时候就会发布消息给他。订阅的方式,通过一个监听数组,把每个组件的render函数都放到有个数组里面,当数据改变后,我们把所有订阅了的组件的监听函数重新执行一遍。这样视图层就得到了改变。因此在dispatch的设计思想就是,先把reducer后的值拿过来,把它赋值给中央,再把组件的值更新下。 storeChange.js的代码,使用es6解构语法如下:
export const storeChange = (store,action) => {
switch (action.type) {
case "HEAD":
return {
...store,
head: action.head
}
case "BODY":
return {
...store,
body:action.body
}
default:
return { ...store}
}
}
复制代码
reudx在大型项目中往往会有很多的输出,因此我们在此也用了一个设计模式,把输出统一,这样便于后期的维护和修改。index.js代码如下:
export * from './createStore';
export * from './storeChange';
复制代码
好了。来到今天的重磅嘉宾了。你觉得是react-redux。当然不是。而是react-redux的中流砥柱,context。
有请context
Context是React的高级API ,使用context可以实现跨组件传值。在react-redux的中就是通过context提供一个全局的store ,拖拽组件的react-dnd,通过Context在组件中分发DOM的Drg和Drop事件。不仅如此,路由组件react-router还可以通过Context管理路由状态等等,可以说相当重量级的嘉宾了。 这是官方的一个描述:
Context的用法
Context的使用基于生产者消费者模式。
父节点作为Context的生产者,而消费者则是父节点下的所有的节点。父节点通过一个静态属性childContextTypes提供给子组件的Context对象属性,并实现一个实例getChildCotext方法,返回一个Context纯对象。而子组件通过一个静态属性contextTypes声明后,才能访问父组件的context对象属性,否则即使属性名没有写错,拿到的对象也是undefined。 App.js我们代码设计如下:
import React, { Component } from "react";
import PropTypes from "prop-types"
import Body from "./component/body/Body"
import Head from "./component/head/Head"
import { createStore, storeChange} from './redux';
// import './App.css';
class App extends Component {
static childContextTypes = {
store: PropTypes.object,
dispatch: PropTypes.func,
subscribe: PropTypes.func,
getStore: PropTypes.func
}
getChildContext() {
const state = {
head: "我是全局head",
body: "我是全局body",
headBtn: "修改head",
bodyBtn: "修改body"
}
const { store,dispatch, subscribe,getStore } = createStore(state,storeChange)
return { store,dispatch,subscribe,getStore}
}
render() {
return (
<div className="App">
<Head />
<Body />
</div>
);
}
}
export default App;
复制代码
static声明一个ChildContextTypes,顶层组件规定要传给子组件Context对象的属性类型,一个getChildContext函数,返回给子组件一个纯对象 ,子组件中接收,子组件目录结构如下:
Body.jsimport React, {Component} from 'react';
import Button from '../Button/Button';
import PropTypes from "prop-types";
export default class Body extends Component {
static contextTypes = {
store: PropTypes.object,
subscribe: PropTypes.func,
getStore: PropTypes.func
}
constructor(props) {
super(props);
this.state = {};
}
componentWillMount () {
const { subscribe } = this.context;
this._upState();
subscribe(()=> this._upState())
}
_upState() {
const { getStore } = this.context;
this.setState({
...getStore()
})
}
render () {
return (
<div>
<div className="body">{this.state.body}</div>
<Button/>
</div>
)
}
}
复制代码
子组件通过一个contextTypes,用来接收父组件传过来的属性。组件中使用了状态就代表他需要订阅,因此我们加载组件的时候就就组件的setState方法push到监听函数里面,这样就能让数据改变后页面能够得到重新渲染。关于setState这篇文章--setState这个API到底怎么样讲解的挺详细的,不是很明白setState背后的原理的骨子可以看看。 同理贴上Head.js:
import React, {Component} from 'react';
import PropTypes from "prop-types"
export default class Head extends Component{
static contextTypes = {
store: PropTypes.object,
subscribe: PropTypes.func,
getStore: PropTypes.func
}
constructor(props) {
super(props);
this.state = { };
}
componentWillMount () {
const { subscribe } = this.context;
this._upState();
subscribe(()=> this._upState())
}
_upState() {
const { getStore } = this.context;
this.setState({
...getStore()
})
}
render() {
return (
<div className="head">{this.state.head}</div>
)
}
}
复制代码
Button.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Button extends Component {
static contextTypes = {
store: PropTypes.object,
dispatch: PropTypes.func,
subscribe: PropTypes.func,
getStore: PropTypes.func
}
constructor(props) {
super(props);
this.state = {};
}
componentWillMount() {
this._upState();
}
_upState() {
const { store } = this.context;
this.setState({
...store
})
}
changeContext(type) {
const { dispatch } =this.context;
const key = type === "HEAD" ? "head":"body";
dispatch({
type: type,
[key]: `我是修改后的${key}`
})
}
render () {
return (
<div className="button">
<div className="btn" onClick={() => {
this.changeContext("HEAD")
}}>改变head</div>
<div className="btn" onClick={() => {
this.changeContext("BODY")
}}>改变body</div>
</div>
)
}
}
复制代码
整个流程走完,context的API在react-redux的用法就是这样的了 这是效果:
欢送context
context总共分为四步:
- ChildContextTypes => 顶层组件中规定类型
- getChildContext 顶层组件中设置传递属性
- 后代组件通过contextTypes 规定数据类型
- 后代组件this.context获取数据
后期React对Context做了调整,但是更方便我们使用,有需要的可以看下我的github上demo.js,一清二楚。
结束语
其实一步步慢慢去了解,就能发现万事万物真的皆是一理,之前面对两个主流框架,react和vue,大家都说vue更简单,更容易上手,于是就先学了vue,刚学习的时候,感觉,好像也不难,后面在公司实习的时候,一直用,感觉用的挺顺手的。就觉得如果要用起来也就那么回事。再到离职后,发现都在用react,去学react,也是同样的感觉。学习可能是一个坎,坚持下跨过去了就好了。一个大三老油条[hahah]现在也是边准备春招的实习面试,边学习,边写文章。分享不到位或是不正确的地方望大家指正。