開場白
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]現在也是邊準備春招的實習面試,邊學習,邊寫文章。分享不到位或是不正確的地方望大家指正。