和TARO一起做SPA--react+redux+dva+mockjs+taro實踐

1. 基礎(chǔ)知識

1.1 this指針

this指針大概是javascript中最令初學(xué)者困惑的語法了寇漫,簡單說稳吮,this指針就是指向函數(shù)或方法運行的上下文環(huán)境斧拍。既然叫上下文環(huán)境,肯定和運行的環(huán)境相關(guān)蚁孔。
在瀏覽器環(huán)境下:

  • 當(dāng)this出現(xiàn)在函數(shù)調(diào)用中,指向的是他運行的上下文:window對象惋嚎;
  • 當(dāng)this出現(xiàn)在對象方法調(diào)用時杠氢,則指向了他運行的上下文:對象本身;
    讓我們舉個例子說明一下:
    function fn(){
        console.log(this);
    }
    var obj={
        fn:fn
    }
    fn();
    obj.fn();

通過運行代碼,你會發(fā)現(xiàn),fn返回的是window對象,而obj.fn返回的則是obj對象
為了加深理解另伍,我們看一個有點迷惑的問題

<!DOCTYPE html>
<html>
    <div id="root"></div>
</html>
<script type="text/javascript"/>
    var e1=document.getElementById("root");
    var getId=document.getElementById
    var e2=getId("root");
</script>

這是一個常見的場景,每次使用document.getElementById方法很麻煩,所以重新定義一個函數(shù) getId,指向document.getElementById方法.
但是你發(fā)現(xiàn),在chrome瀏覽器中調(diào)用getId方法居然系統(tǒng)會拋一個異常:

test.htm:9 Uncaught TypeError: Illegal invocation 

發(fā)生異常的原因就在于,getElementById內(nèi)部實現(xiàn)使用了this指針指向document對象.但是,當(dāng)你用自己定義的getId方法時,getElementById已經(jīng)由對象調(diào)用變成了方法調(diào)用,this指針被指向了window對象.

既然this指針有這樣的不確定性鼻百,那么,自然就可以想到如何根據(jù)需要變更他的指向摆尝。
變更this指針指向有兩種方法愕宋,定義時綁定和運行時綁定。

  • 定義時綁定 bind
  • 運行時綁定 apply/call
    還是讓我們看個例子:
    對于我們上個getId的例子,你只要這樣調(diào)用就可以了
    定義時通過bind方法綁定
    const getId=document.getElementById.bind(document);
    const e=getId("root");

運行時通過call方法綁定

    const e=getId.call(document,"root")

運行時通過apply方法綁定

     const e=getId.apply(document,["root"])

通過上面的例子也可以看到call和apply兩者的區(qū)別是:apply綁定時结榄,傳入的參數(shù)為數(shù)組形式中贝,而call綁定則是采用枚舉的方式。所以如果getId方法需要使用apply方法時,必須將參數(shù)包裝成數(shù)組的形式.

1.2 高階函數(shù)

在javascript語言中,函數(shù)是一類成員,函數(shù)可以作為變量,也可以作為輸入?yún)?shù)和返回參數(shù).將函數(shù)作為輸入?yún)?shù)或輸出參數(shù)的函數(shù)稱之為高階函數(shù).后面我將會帶大家一起了解一下高階函數(shù).

1.2.1 閉包

讓我們看一下第一個高階函數(shù),閉包.
閉包利用了javascript函數(shù)作用域內(nèi)變量被引用不會消除的特性.閉包被應(yīng)用的場景非常多.
讓我們先看一個獲取遞增ID的例子,首先,讓我們看一下傳統(tǒng)的方法,傳統(tǒng)的方法獲取遞增ID,你需要先做一個全局變量.

let globalID=0
function getID(){
    return globalID++;
}
console.log(getID(),getID(),getID())

這種方法由于使用了全局變量,任何一個人都有可能不經(jīng)意的修改globalID的值導(dǎo)致你的方法失效.
采用閉包的寫法,你先創(chuàng)建一個crGetID方法通過閉包保存ID,并返回getID函數(shù).然后通過getID方法獲取ID

function crGetID(){
    let id=0;
    return function(){
        return id++;
    }
}
var getID=crGetID();
console.log(getID(),getID(),getID())

這樣,沒有人可以直接修改你的ID值.

1.2.2 currying

讓我們再看一個閉包的應(yīng)用:currying,currying解決的問題是把一個函數(shù)的多個參數(shù)轉(zhuǎn)換為單參數(shù)函數(shù).
舉個例子,假設(shè)我們需要累計一個用戶7天的數(shù)據(jù):

function add(d1,d2,d3,d4,d5,d6,d7){
    return d1+d2+d3+d4+d5+d6+d7
}

如果是30天,可能需要30個輸入?yún)?shù),如果不定天數(shù)呢?
采用currying則可以解決這個問題:

function curryAdd(){
    let s=[];
    return function(...arg){
        if (arg.length==0){
            return s.reduce(function(p,v){return p+v},0)
        }else{
            s.push(...arg)
            console.log("s",s);
        }
    }
}
var ca=curryAdd();
ca(1);
ca(2);
ca(3);
ca(4);
console.log(ca());

通過將一個函數(shù)currying后,函數(shù)可以隨時被調(diào)用,直到輸入?yún)?shù)為空時才進(jìn)行計算.
閉包的特性使之成為javascript中運用最廣的特性.后續(xù)在代碼中,我們還會繼續(xù)看到大量的閉包用法.

1.3 es6語法

react 大量使用了es6的語法臼朗,如果你對javsascript的印象還停留在原始的印象里邻寿,你可能根本沒法看懂react的代碼。所以在這里我們簡單對用到的es6語法做一些介紹视哑,并盡可能以react實際使用作為學(xué)習(xí)的例子绣否。詳細(xì)的es6語法介紹,可以參考相關(guān)的技術(shù)文檔挡毅。

1.3.1變量解析

  • 數(shù)組變量解析
    es6 支持對數(shù)組直接進(jìn)行解析蒜撮,舉個例子,如果需要對變量x,y互換值,傳統(tǒng)的做法是:
function(x,y){
    var t;
    t=x;
    x=y;
    y=t;
}

如果用數(shù)組解析段磨,就容易多了:

let [x,y]=[y,x]

數(shù)組解析可以用到輸入?yún)?shù)傳遞

    function test ([x,y,z]){
        return x+y+z;
    }
    console.log(test([1,2,3]))

上面的例子,打印出來的結(jié)果是6.
還可以用到一次返回多個參數(shù):

    function retMult(){
        return [1,2,3]
    }
    let [x,y,z]=retMult();
    console.log(x,y,z)

函數(shù)會打印出 1,2,3

  • 對象變量解析
    對象解析用的更廣泛取逾,讓我們舉個簡單的例子:
    let {x:x,y:y}={x:2,y:3}
    console.log(x,y);

函數(shù)打印結(jié)果 2,3
還可以簡寫為:

    let {x,y}={x:2,y:3}
    console.log(x,y);

讓我們看一下下面的例子,如果對象屬性名稱和變量名稱可以更進(jìn)一步進(jìn)行簡寫:

    let x=1;
    let obj={x};  //相當(dāng)于 obj={x:x}
    console.log(obj.x);

和數(shù)組解析一樣,對象解析大量應(yīng)用在輸入?yún)?shù)的傳值上,讓我們舉一個redux的實際應(yīng)用的例子(這個例子里,假設(shè)state對象僅包含一個value屬性):

function  reducer({value},{type,payLoader}){
    switch(type){
        case "calc":
            return  {value:value+payLoader}
        default:
            return {value}
    }
}

這個例子里,使用了對象的解析,代碼更加簡潔和異動.如果不使用對象解析,你的代碼是這樣的:

function reducer(state,action){
    switch(action.type){
        case "calc":
            return {value:state.value+action.payLoader}
        default:
            return state;
    }
}

1.3.2箭頭函數(shù)

箭頭函數(shù)可以讓代碼更加簡潔和直觀苹支,另外砾隅,由于箭頭函數(shù)對this指針的特殊處理,因此债蜜,被大量的運用晴埂。
讓我們還是以數(shù)組提供的map函數(shù)為例子說明:

let arr=[1,2,3,4];
let m=arr.map(v=>v*2);

使用箭頭函數(shù),即簡潔又直觀,對數(shù)組中的每個元素直接乘以2.
如果使用傳統(tǒng)的方式,你需要這樣寫:

let n=arr.map(function(v){
    return v*2;
})
console.log(n);

使用箭頭函數(shù)需要注意以下幾點:

  • 如果是一個參數(shù),可以省略(),如果多個參數(shù)或沒有參數(shù),則必須使用()
  • 如果函數(shù)直接返回箭頭后的表達(dá)式,可以不加{},否則,需要在箭頭后使用{}
    讓我們再舉個數(shù)組提供的reduce方法的實際例子:
var r=[1,2,3,4].reduce((p,n)=>p+n);
console.log(r);

這個例子通過reduce函數(shù)計算數(shù)字元素的累加和,reduce函數(shù)的輸入?yún)?shù)是一個函數(shù),函數(shù)的輸入?yún)?shù)分別為累加之和p以及下一個元素n.
如果用傳統(tǒng)的方法,你需要這樣寫:

var r=[1,2,3,4].reduce(function(p,n){
    return p+n;
})
console.log(r);

1.3.3類

ES6提供了類,類實際上就是一個語法糖.讓我們還是通過一個具體的例子來看一下類的實現(xiàn):

class Counter extends Component{
    constructor(props){
        super(props)
    }
    sub(){
        let value=this.state.value-1;
        this.setState({value})
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
    }
    render(){
        return (
            <div>
                <button onClick={this.add.bind(this)}>+</button>
                <button onClick={this.desc.bind(this)}>-</button>
            </div>
        )
    }
}

這個例子說明了ES6的類的用法.
首先,類的定義語法是:

class  Name{
}

你的類可以繼承自另一個類,在剛才的例子里我們的類Counter繼承自React的Component類

class Counter extends Component{
}

你可以根據(jù)需要實現(xiàn)類的構(gòu)建器,構(gòu)建器可以通過super方法引用父類的構(gòu)建器:

    constructor(props){
        super(props)
    }

類的方法可以縮寫為methodName(){}的形式

    sub(){
        let value=this.state.value-1;
        this.setState({value})
    }

1.3.4裝飾器

1.4 異步函數(shù)

2. 代碼框架

2.1nodejs

2.2webpack

2.3react

2.3.1第一個react程序

由于react需要依賴大量的依賴庫,如通過babel對es6的轉(zhuǎn)化寻定,css的渲染…對于新手來說儒洛,可能這一大堆配置就讓人望而卻步。為了簡化開發(fā)環(huán)境的搭建狼速,讓我們直接用create-react-app 搭建腳手架(具體命令含義可以先不用理解):
首先,我們安裝create-react-app

npm install -g create-react-app

安裝成功后,就可以開始使用

mkdir basic
create-react-app basic

basic是我們第一個react程序的名稱,create-react-app命令會運行一段時間,幫我們搭建腳手架和開發(fā)環(huán)境.命令執(zhí)行后,我們開始啟動我們的應(yīng)用:

cd basic
yarn start

yarn start命令后,webpack會啟動webpack-dev-server跟蹤我們代碼修改,然后自動啟動一個端口為3000的HttpServer幫我們進(jìn)行調(diào)試,并且很貼心的彈出URL為http://localhost:3000的瀏覽器頁面,顯示我們的第一個react應(yīng)用.
好,讓我們開始學(xué)習(xí)第一個react程序,首先,讓我們看一下腳手架幫我們搭建的目錄結(jié)構(gòu)晶丘。

,create-react-app創(chuàng)建的目錄結(jié)構(gòu)

其中/public/index.html就是我們的頁面模板,所有的組件都會渲染在這個文件上.
讓我們看一下第一個組件:/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

這個組件很簡單,重要的是這一句:

ReactDOM.render(<App />, document.getElementById('root'));

這個是React的語句,意思是在index.html頁面ID為root的元素上渲染App組件.其中index.html頁面放置在public目錄下.
而App就是React組件,系統(tǒng)如何區(qū)分React組件和DOM組件呢?很簡單,在React中,首字母為大寫的就是React組件,小寫字母為DOM組件.
App組件非常簡單,讓我們也看一下:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}
export default App;

好唐含,現(xiàn)在讓我們開發(fā)一個計數(shù)器程序來學(xué)習(xí)一下React開發(fā).首先,讓我們自己定義一個Counter組件.
首先,我們在src根目錄下新建一個counter.js文件

import React,{Component} from 'react'
class Counter extends Component{
    constructor(){
        super()
        this.state={value:0}
    }
    render(){
        return (
            <div>
                <span>{this.state.value}</span>
                <button onClick={this.add.bind(this)}>+</button>
                <button onClick={this.desc.bind(this)}>-</button>
            </div>
        )
    }
    desc(){
        let value=this.state.value-1;
        this.setState({value})
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
    }
} 
//將組件導(dǎo)出模塊
export default Counter

然后,修改index.js文件,渲染我們新開發(fā)的Counter組件:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
-import App from './App';
+import Counter from './counter'
import * as serviceWorker from './serviceWorker';
-ReactDOM.render(<App />, document.getElementById('root'));
+ReactDOM.render(<Counter />, document.getElementById('root'));
serviceWorker.unregister();

頁面會自動刷新修改后的內(nèi)容,(是不是很驚訝)顯示結(jié)果.點擊 + 和 - 按鈕系統(tǒng)會自動顯示最新的counter.
讓我們來分析一下代碼:

首先 ,我們的Counter組件繼承自React.Component.我們的Counter組件繼承Component組件,并且實現(xiàn)了構(gòu)建器.在構(gòu)建器中,完成了state值的初始化.

class Counter extends Component{
    constructor(){
        super()
        this.state={value:0}
    }
}

讓我們看接下來的渲染部分的處理:

    render(){
        return (
            <div>
                <span>{this.state.value}</span>
                <button onClick={this.add.bind(this)}>+</button>
                <button onClick={this.desc.bind(this)}>-</button>
            </div>
        )
    }

render方法是React組件的核心部分,主要完成組件的表現(xiàn).在這里,render方法返回了一個jxs的代碼段.所謂的jxs,簡單說就是嵌入了react語句的html代碼段.
在這里,你特別需要注意的是,React會自動跟蹤state值的變化進(jìn)行渲染,因此,你不需要像傳統(tǒng)開發(fā)一樣手動渲染數(shù)據(jù),只需要簡單的標(biāo)明會發(fā)成變更的數(shù)據(jù)即可:

                <span>{this.state.value}</span>

在這里,{}表示的是里面的部分是由react代碼構(gòu)成.
這個代碼段有幾個特別需要注意的地方:

  • return語句直接返回JSX時,必須用()進(jìn)行包裹,下面的語句由于<div>前沒有(),會直接報錯:
    render(){
        return <div>{this.state.value}</div>  
    }
  • html代碼段必須有一個根元素,因此,以下的代碼是錯誤的:
    render(){
        return (
                <span>{this.state.value}</span>
                <button onClick={this.add.bind(this)}>+</button>
                <button onClick={this.desc.bind(this)}>-</button>
        )
    }
  • 和HTML事件命名機(jī)制不同,讓我們對比一下React的寫法:
  <button onClick={this.add.bind(this)}>+</button>

下面是HTML的寫法:

 <button onclick="test()">test</button>

有三個重要的區(qū)別:
1.React事件觸發(fā)是駱駝命名方式.而HTML的觸發(fā)方式是全部小寫;
2.React事件觸發(fā)是函數(shù)名,而HTML的觸發(fā)方式是函數(shù)執(zhí)行代碼塊;
3.React事件處理函數(shù)this指針不會綁定任何對象,而HTML指針會自動綁定到window對象;

  • 如果處理state,則React事件處理函數(shù)需要綁定this指針到組件
    這就是以下代碼的原因,通過bind方法將函數(shù)this指針綁定到React Component:
  <button onClick={this.add.bind(this)}>+</button>
  • state的處理原則
    讓我們看一下事件處理方法的實現(xiàn):
    desc(){
        let value=this.state.value-1;
        this.setState({value})
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
    }

代碼很簡單,但是有幾個需要注意的地方:

  • 不能直接修改state的值,必須通過this.setState方法修改,否則,系統(tǒng)不會進(jìn)行渲染.
  let value=this.state.value+1
  this.state.value=value
  • state的原始值不能修改,因此,以下代碼是無效的:
   let value=this.state.value++; //this.state.value++修改了原始的state的值
   this.setState({value})

第一個react程序結(jié)束了,React的邏輯很簡單浅浮,每個組件都有一個state值,組件通過監(jiān)控state的狀態(tài)變化實現(xiàn)頁面渲染.
最后,別忘了需要從模塊中導(dǎo)出我們的組件:

export default Counter

導(dǎo)出是,如果不增加default參數(shù),導(dǎo)入時需要將組件名稱用{}括起來.一個模塊中智能有唯一的一個default組件.

2.3.2 React組件間通訊

從第一個例子我們可以發(fā)現(xiàn),React組件開發(fā)很容易,通過監(jiān)控組件state值的變化,實現(xiàn)自動的渲染,極大的減輕了開發(fā)的工作量.但是每個組件都有自己的state,如果多個組件需要通訊,問題就變得復(fù)雜了.
讓我們看下面的這個例子,在這個例子,組件Control由3個Counter構(gòu)成,每個Counter都可以自動增減,CounterControl顯示的值是3個Counter的累加值.
這個例子顯示了組件間如何進(jìn)行通信.
先讓我們看一下CounterControl組件的代碼:

import React,{Component } from "react";
import Counter from "./counter"
class counterControl extends Component{
    constructor(){
        super();
        //設(shè)置組件的初始值
        this.state={value:0}
    }
    //這個地方需要特別注意,change是Counter組件每次點擊發(fā)生變化的值
    change(change){
        //不可以修改state的原始值
        let value=this.state.value;
        value+=change;
        //必須通過this.setState方法進(jìn)行state的修改
        this.setState({value:value})
    }
    render(){
        return(
            <div>
                <Counter name={"one"} change={this.change.bind(this)}/>
                <Counter name={"two"}  change={this.change.bind(this)}/>
                <Counter name={"three"}  change={this.change.bind(this)}/>
                <div>value:{this.state.value}</div>
            </div>
        )
    }
}
export default counterControl;

Counter組件也發(fā)生了變化,增加了name屬性和change方法.

<Counter name={"three"}  change={this.change.bind(this)}/>

由于組件彼此state獨立,因此,組件之間的通訊就落到了change方法里.讓我們看一下change方法的實現(xiàn):

    //這個地方需要特別注意,change是Counter組件每次點擊發(fā)生變化的值
    change(change){
        //不可以修改state的原始值
        let value=this.state.value;
        value+=change;
        //必須通過this.setState方法進(jìn)行state的修改
        this.setState({value:value})
    }

change方法實際是CounterControl通過屬性傳遞給Counter子組件的回調(diào)函數(shù).他的實現(xiàn)原理是每次Counter組件被點擊時,把Counter組件state值的變化回調(diào)至CounterControl,從而實現(xiàn)CounterControl的State值的變化.
讓我們看一下Counter組件:

import React,{Component} from 'react'
class Counter extends Component{
    constructor(props){
        super(props)
        this.state={value:0}
      }
    desc(){
        let value=this.state.value-1;
        this.setState({value})
        this.props.change(-1)
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
        this.props.change(1);
    }
    render(){
        return (
            <div>
                <span>{this.props.name}:{this.state.value}</span>
                <button onClick={this.add.bind(this)}>+</button>
                <button onClick={this.desc.bind(this)}>-</button>
            </div>
        )
    }
}
export default Counter

重點看一下組件的onClick方法:

    desc(){
        let value=this.state.value-1;
        this.setState({value})
        this.props.change(-1)
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
        this.props.change(1);
    }

無論是add還是desc方法,Counter在完成自己state變更的同時,都需要調(diào)用通過props屬性傳遞的change方法回調(diào)CounterControl提供的chanage方法,實現(xiàn)state變化的通知.

2.3.3 優(yōu)化

上面的例子我們發(fā)現(xiàn),Counter組件如果需要整合到CounerControl組件中,就必須進(jìn)行修改.
原來Counter組件的事件如下:

    desc(){
        let value=this.state.value-1;
        this.setState({value})
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
    }

修改后

    desc(){
        let value=this.state.value-1;
        this.setState({value})
        //增加了change的回調(diào)方法
        this.props.change(-1)
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
        //增加了change的回調(diào)方法
        this.props.change(1);
    }

也就是說,Counter組件并不是一個通用的組件.造成這種情況的原因,是由于Counter組件增加了過多的業(yè)務(wù)邏輯.
如果把組件的顯示和業(yè)務(wù)邏輯進(jìn)行剝離成內(nèi)部組件和容器組件,就可以解決這個問題.內(nèi)部組件只負(fù)責(zé)顯示,容器組件則負(fù)責(zé)具體的業(yè)務(wù)邏輯和state處理.讓我們看一下如何進(jìn)行剝離:
新的NgCounter內(nèi)部組件代碼如下:

class NgCounter extends Component{
    render(){
        return (
            <div>
                <span>{this.props.name}:{this.props.value}</span>
                <button onClick={this.props.add}>+</button>
                <button onClick={this.props.sub}>-</button>
            </div>
        )        
    }
}

剝離后的NgCounter組件不再進(jìn)行任何業(yè)務(wù)邏輯的處理,也不處理state相關(guān)的數(shù)據(jù).它只是按照傳遞的屬性值進(jìn)行顯示或回調(diào).
我們管這種不處理任何state的組件稱之為無狀態(tài)組件.無狀態(tài)組件可以進(jìn)一步簡化為函數(shù),如下所示:

import React,{Component} from 'react'
function NgCounter(props){
    return (
        <div>
            <span>{props.name}:{props.value}</span>
            <button onClick={props.add}>+</button>
            <button onClick={props.sub}>-</button>
        </div>
    )
}

無狀態(tài)組件函數(shù)由于沒有this指針,屬性props由容器組件傳遞.
讓我們看一下容器組件如何進(jìn)行處理

import React,{Component} from 'react'
class Container extends Component{
    constructor(props){
        super(props);
        this.state={value:0}
    }
    add(){
        this.setState({value:this.state.value+1})
        //如果嵌入CounterControl則需要增加以下方法
        this.props.add();
    }
    sub(){
        this.setState({value:this.state.value-1})
        //如果嵌入CounterControl則需要增加以下方法
        this.props.sub();
    }
    render(){
        return(
            <NgCounter name="ngCounter" add={this.add.bind(this)} sub={this.sub.bind(this)} value={this.state.value}/>
        )
    }
}
export default Container;

通過內(nèi)部組件和容器組件的拆分,如果組件需要嵌入其他組件,則只需要修改容器組件即可.內(nèi)部組件不需要進(jìn)行任何調(diào)整.
需要特別注意的是,此時導(dǎo)出的組件為容器組件.

我們把CounterControl也進(jìn)行了改造,相應(yīng)的代碼如下:

import React,{Component} from 'react';
import NgCounter from './ngCounter'
//內(nèi)部無狀態(tài)組件退化為函數(shù)
function NgCounterControl(props){
    return(
        <div>
            <NgCounter name={"one"} add={props.add} sub={props.sub}/>
            <NgCounter name={"two"}  add={props.add} sub={props.sub}/>
            <NgCounter name={"three"}  add={props.add} sub={props.sub}/>
            <div>value:{props.value}</div>
        </div>
    )
}
//容器組件負(fù)責(zé)具體的業(yè)務(wù)邏輯和state的處理
class Container extends Component{
    constructor(props){
        super(props);
        this.state={value:0}
    }
    add(){
        this.setState({value:this.state.value+1})
    }
    sub(){
        this.setState({value:this.state.value-1})
    }
    render(){
        return (
            //返回內(nèi)部組件
            <NgCounterControl add={this.add.bind(this)} 
            sub={this.sub.bind(this)}
            value={this.state.value} />
        )
    }
}
//導(dǎo)出容器組件
export default Container;

2.3.4 總結(jié)

通過上面的例子,我們可以發(fā)現(xiàn),雖然我們把組件拆分為內(nèi)部組件和容器組件,實現(xiàn)了內(nèi)部組件的獨立性,但是,由于React組件彼此都維護(hù)自己的state,當(dāng)多個組件需要同步state值的時候,情況還是變得很復(fù)雜,組件必須通過props屬性傳遞回調(diào)方法層層調(diào)用.因此,對于多個組件協(xié)調(diào)工作時,這種實現(xiàn)方法就顯得很笨拙而且效率低下.

2.4Ant.Design的整合

在開始redux模塊前,讓我們先介紹一下Ant.Design模塊.
到目前為止,我們自己開發(fā)的組件都是基于html的原生元素.實際上,Ant.Design已經(jīng)幫我們精心設(shè)計了大量優(yōu)秀的組件,我們可以直接使用,這一部分,我們將介紹給大家如何使用Ant.Design

2.4.1引入Ant.Design

首先,讓我們安裝Ant.Design

yarn add antd

修改你的App.css,在第一行加入:

@import '~antd/dist/antd.css';

讓我們新增加一個antd的組件,簡單演示一下如何使用Ant.Design.讓我們在/src目錄下增加一個antd.js文件,簡單實現(xiàn)一個無狀態(tài)的組件.
這個組件里我們簡單使用了Row,Col,Button,Calendar,Rate,Card,Steps 幾個組件.

import React from 'react'
import "./App.css"
import {Row,Col,Button,Calendar,Rate,Card,Steps } from 'antd'
export default function(){
    const Step = Steps.Step;
    return (
        <Row>
            <Col span={12}>
                <Card title="Antd 示例">
                    <Rate allowHalf defaultValue={2.5} />
                    <Steps current={1}>
                        <Step title="Finished" description="This is a description." />
                        <Step title="In Progress" description="This is a description." />
                        <Step title="Waiting" description="This is a description." />
                    </Steps>
                    <Button type="primary">hello,world</Button>
                </Card>
            </Col>
            <Col span={12}><Calendar/></Col>
        </Row>
    )
}

下一步,修改index.js文件

//導(dǎo)入剛才新建的組件
import Antd  from './antd'
//渲染該組件
ReactDOM.render(<Antd/>, document.getElementById('root'));

現(xiàn)在我們應(yīng)該可以看到Ant.Design開始正式工作了.

2.4redux組件

2.4.1 reduct組件原理

reduct組件由store action reducer構(gòu)成:

  • store:全局唯一和共享的數(shù)據(jù)存儲;store的主要方法有
    • dispatch:負(fù)責(zé)調(diào)用reducer處理action,生成新的state
    • subscribe:負(fù)責(zé)監(jiān)聽state的變化,如果state發(fā)生了變化,則調(diào)用相關(guān)的回調(diào)函數(shù)進(jìn)行處理
  • action:由動作類型type和攜帶的數(shù)據(jù)構(gòu)成,reducer負(fù)責(zé)根據(jù)action的type進(jìn)行相應(yīng)的處理;
  • reducer:負(fù)責(zé)根據(jù)action處理state,并且保證每次reducer操作,都必須返回新的state;

2.4.2 reduct的使用

讓我們通過一個實際的例子學(xué)習(xí)怎樣使用reduct,
首先,我們需要安裝redux

yarn add redux

實現(xiàn)reducer,創(chuàng)建store

import React,{Component} from 'react'
import PropTypes from 'prop-types'
import {createStore} from 'redux'
const reducer=(state,action)=>{
    console.log(state.value)
    switch(action.type){
        case "calc":
            return {value:state.value+action.payload}
        default:
            return state;
    }
}
const initState={value:0};
const store=createStore(reducer,initState);

創(chuàng)建store后,我們需要所有的組件都能夠訪問該store,因此,我們需要把store放到所有組件的父容器上.在這里,我們通過新建一個Provider組件實現(xiàn)store的保存:

class Provider extends Component{
    getChildContext(){
        return {
            store:this.props.store
        }
    }
    render(){
        return this.props.children
    }
}
Provider.childContextTypes={
    store:PropTypes.object
} 

請注意getChildContext方法,為了簡化store的傳遞,React提供了一個Context對象.需要使用Context的父組件只要實現(xiàn)getChildContext方法返回Context對象.并且設(shè)置該屬性的類型為PropTypes.object即可.
因為Provider作為父組件,內(nèi)部會嵌套子組件,所以render方法直接返回this.props.children即可.
讓我們 看一下如何使用Provide組件:

        <Provider store={store}>
            <ReduxCounter name={"one"} />
            <ReduxCounter name={"two"} />
            <ReduxCounter name={"three"} />
            <Panel/>
        </Provider>     

this.props.children指向的即是Provider組件嵌套的內(nèi)容.
讓我們看一下ReduxCounter組件的實現(xiàn):

function ReduxCounter(props){
    return (
        <div>
            <span>{props.name}:{props.value}</span>
            <button onClick={props.add}>+</button>
            <button onClick={props.sub}>-</button>
        </div>        
    )
}

ReduxCounter 組件是一個內(nèi)部無狀態(tài)組件,只負(fù)責(zé)渲染

class ReduxCounterContainer extends Component{
    constructor(props,context){
        super(props,context);
        this.state={value:0}
        this.store=this.context.store;
    }
    add(){
        this.setState({value:this.state.value+1})
        this.store.dispatch({
            type:"calc",
            payload:1
        })
    }
    sub(){
        this.setState({value:this.state.value-1})
        this.store.dispatch({
            type:"calc",
            payload:-1
        })
    }
    render(){
        return(
            <ReduxCounter name={this.props.name} value={this.state.value} add={this.add.bind(this)} sub={this.sub.bind(this)}/>
        )
    }
}
ReduxCounterContainer.contextTypes={
    store:PropTypes.object
}

所有的邏輯和狀態(tài)的處理都由ReduxCounterContainer組件完成.
在這里,需要特別注意的是

ReduxCounterContainer.contextTypes={
    store:PropTypes.object
}

如我們之前所說,所有的子組件,如果希望使用父組件提供的context,必須聲明該組件的contextTypes為PropTypes.object

下一步,讓我們看一下Panel組件的實現(xiàn)

class Panel extends Component{
    constructor(props,context){
        super(props,context);
        context.store.subscribe(this.change.bind(this));
    }
    change(){
        this.setState(this.context.store.getState());
    }
    render(){
        return (
            <div>{this.context.store.getState().value}</div>
        )
    }
}
Panel.contextTypes={
    store:PropTypes.object
}

Panel組件很簡單,只需要通過跟蹤store的state變化,展示子state的value值即可.
因此,Panel組件需要接受store的回調(diào)

        context.store.subscribe(this.change.bind(this));

最后,讓我們看一下ReduxCounterControl組件的實現(xiàn)

function  ReduxCounterControl(){
    return (
        <Provider store={store}>
            <ReduxCounterContainer name={"one"} />
            <ReduxCounterContainer name={"two"} />
            <ReduxCounterContainer name={"three"} />
            <Panel/>
        </Provider>        
    )
}
export default ReduxCounterControl;

至此,我們完成了redux的使用,總結(jié)一下,redux使用包括以下幾步:

  • 創(chuàng)建reducer
  • 創(chuàng)建store
  • 創(chuàng)建Provider,用來保存和傳遞store
  • 需要跟蹤store值的組件,調(diào)用store的subscribe方法監(jiān)控state值的變化
  • 各組件通過store.dispatch方法調(diào)用reducer處理并返回state
  • 組件發(fā)現(xiàn)state值發(fā)生變化,自動渲染
    下一節(jié)我們將學(xué)習(xí)如何使用react-redux簡化這個流程

2.4.3 react-redux的使用

2.5mockjs

2.6dva

2.7taro

2.8taro-ui

3 開發(fā)環(huán)境搭建

3.1搭建taro腳手架項目

首先,讓我們搭建taro的腳手架,這個例子里我們假設(shè)項目名稱為wos,則在命令行下運行
taro init wos
回答系列問題后,命令行會自動創(chuàng)建以wos為文件夾的腳手架項目.
這個教程里,是否使用TypeScript選擇了否,css預(yù)處理器選擇了less,模板選擇了redux
如下所示:

 Taro v1.2.4

Taro即將創(chuàng)建一個新項目!
Need help? Go and open issue: https://github.com/NervJS/taro/issues/new

? 請輸入項目介紹! wos
? 是否需要使用 TypeScript 捷枯? No
? 請選擇 CSS 預(yù)處理器(Sass/Less/Stylus) Less
? 請選擇模板 Redux 模板

執(zhí)行成功后,可以開始編譯微信小程序:

cd wos
npm run dev:weapp

命令會執(zhí)行一會兒,當(dāng)出現(xiàn)

監(jiān)聽文件修改中...

說明編譯完成,可以打開微信小程序開發(fā)工具,新建項目,選擇wos文件夾后,可以看到小程序已經(jīng)可以正常運行.

3.2整合tara-ui

在命令行下輸入
yarn add taro-ui
現(xiàn)在,taro-ui已經(jīng)整合成功,我們可以簡單修改pages/indexs頁面測試一下

+ import {aButton} from 'taro-ui'
  render () {
    return (
      <View className='index'>
      +<AtButton type='primary'>tryme</AtButton>
        <Button className='add_btn' onClick={this.props.add}>+</Button>
        <Button className='dec_btn' onClick={this.props.dec}>-</Button>
        <Button className='dec_btn' onClick={this.props.asyncAdd}>async</Button>
        <View><Text>{this.props.counter.num}</Text></View>
        <View><Text>Hello, World</Text></View>
      </View>
    )
  }

再次運行
npm run dev:weapp
打開微信小程序開發(fā)工具,這時候你可以看到,首頁里,已經(jīng)多了一個藍(lán)色的按鈕.
至此,我們已經(jīng)搭建了taro的開發(fā)環(huán)境.

3.3整合dva框架

下面的步驟,我們將整合dva框架.首先,我們先安裝相關(guān)的依賴庫
yarn add dva-core dva-loading
我們把所有的配置都集中在一起,在src目錄下新建一個config文件夾,生成index.js,代碼如下:

// 請求連接前綴
export const baseUrl = 'http://localhost:3721';
// 輸出日志信息
export const noConsole = false;

dva會按照路由多model進(jìn)行分層管理,在taro框架里,我們沒有使用umi,所以需要統(tǒng)一管理model,讓我們在/src目錄下新建models文件夾,生成index.js文件,我們現(xiàn)在還沒有model需要dva管理,因此,先空著.

export default [
]

我們讓dva來管理我們的store,在src目錄下新建util文件夾,生成dva.js文件,代碼如下.

import Taro from '@tarojs/taro'
import { create } from 'dva-core'
import { createLogger } from 'redux-logger'
import createLoading from 'dva-loading'

let app;
let store;
let dispatch;

function createApp(opt) {
  // redux日志
  opt.onAction = [createLogger()];
  app = create(opt);
  app.use(createLoading({}));

  // 適配支付寶小程序
  if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) {
    global = {};
  }
  if (!global.registered) 
    opt.models.forEach(
      model =>{
        app.model(model);
        console.log(model)
      });

  global.registered = true;
  app.start();
  store = app._store;
  app.getStore = () => store;

  dispatch = store.dispatch;
  app.dispatch = dispatch;
  return app;
}

export default {
  createApp:createApp,
  getDispatch() {
    return app.dispatch
  }
}

讓我們封裝一下http請求你的工具類,同樣,在src/util/目錄下新建request.js文件,代碼如下:

import Taro from '@tarojs/taro';
import { baseUrl, noConsole } from '../config';

const request_data = {
  platform: 'wap',
  rent_mode: 2,
};

export default (options = { method: 'GET', data: {} }) => {
  if (!noConsole) {
    console.log(`${new Date().toLocaleString()}【 M=${options.url} 】P=${JSON.stringify(options.data)}`);
  }
  return Taro.request({
    url: baseUrl + options.url,
    data: {
      ...request_data,
      ...options.data
    },
    header: {
      'Content-Type': 'application/json',
    },
    method: options.method.toUpperCase(),
  }).then((res) => {
    console.log("res:",res);
    const { statusCode, data } = res;
    if (statusCode >= 200 && statusCode < 300) {
      if (!noConsole) {
        console.log(`${new Date().toLocaleString()}【 M=${options.url} 】【接口響應(yīng):】`,res.data);
      }
      if (data.status !== 'ok') {
        Taro.showToast({
          title: `${res.data.error.message}~` || res.data.error.code,
          icon: 'none',
          mask: true,
        });
        console.error(`${res.data.error.message}~` || res.data.error.code);
      }
      return data;
    } else {
      throw new Error(`網(wǎng)絡(luò)請求錯誤滚秩,狀態(tài)碼${statusCode}`);
    }
  })
}

典型的dva的開發(fā)目錄如下:

每個文件件都需要4個文件,太復(fù)雜,所以我們在根目錄下增加一個腳本template.js,自動生成文件:

/**
 * pages模版快速生成腳本,執(zhí)行命令 npm run tep `文件名`
 */

const fs = require('fs');

const dirName = process.argv[2];

if (!dirName) {
  console.log('文件夾名稱不能為空!');
  console.log('示例:npm run tep test');
  process.exit(0);
}

// 頁面模版
const indexTep = `import Taro, { Component } from '@tarojs/taro';
import { View } from '@tarojs/components';
import { connect } from '@tarojs/redux';
import './index.scss';

@connect(({${dirName}}) => ({
  ...${dirName},
}))
export default class ${titleCase(dirName)} extends Component {
  config = {
    navigationBarTitleText: '${dirName}',
  };

  componentDidMount = () => {

  };

  render() {
    return (
      <View className="${dirName}-page">
        ${dirName}
      </View>
    )
  }
}
`;

// scss文件模版
const scssTep = `@import "../../styles/mixin";

.${dirName}-page {
  @include wh(100%, 100%);
}
`;

// model文件模版
const modelTep = `import * as ${dirName}Api from './service';

export default {
  namespace: '${dirName}',
  state: {

  },

  effects: {
    * effectsDemo(_, { call, put }) {
      const { status, data } = yield call(${dirName}Api.demo, {});
      if (status === 'ok') {
        yield put({ type: 'save',
          payload: {
            topData: data,
          } });
      }
    },
  },

  reducers: {
    save(state, { payload }) {
      return { ...state, ...payload };
    },
  },

};
`;
// service頁面模版
const serviceTep = `import Request from '../../utils/request';

export const demo = data => Request({
  url: '路徑',
  method: 'POST',
  data,
});
`;
fs.mkdirSync(`./src/pages/${dirName}`); // mkdir $1
process.chdir(`./src/pages/${dirName}`); // cd $1
fs.writeFileSync('index.js', indexTep);
fs.writeFileSync('index.scss', scssTep);
fs.writeFileSync('model.js', modelTep);
fs.writeFileSync('service.js', serviceTep);

console.log(`模版${dirName}已創(chuàng)建,請手動增加models`);

function titleCase(str) {
  const array = str.toLowerCase().split(' ');
  for (let i = 0; i < array.length; i++) {
    array[i] = array[i][0].toUpperCase() + array[i].substring(1, array[i].length);
  }
  const string = array.join(' ');
  return string;
}
process.exit(0);

然后,在package.json文件中的scripts下增加一行
"g":"node template"
然后運行:
npm run g home
可以看到,在pages目錄下新增加了 home文件夾以及下面的4個文件.
至此,我們完成了dva項目的整合,后面,我們將開始完善代碼.

3.4整合mock

首先淮捆,讓我們加載mock依賴庫
yarn add mocker-api mockjs --dev
在項目根目錄下新建 mock文件夾,新建index.js文件郁油。輸入以下代碼:

const delay = require('mocker-api/utils/delay');
const mockjs=require('mockjs');
const data= {
    'GET /api/user': {
        id: 1,
        username: 'kenny',
        sex: 6
    },  
    'GET /api/hi':(req,res)=>{
        res.json(
            {
                id:1,
                //query 方法獲取Get參數(shù),如 /api/hi?name=tony
                username:req.query["name"],
            }
        )
    },
    //可以直接使用mockjs生成mock數(shù)據(jù)
    'GET /api/mock':mockjs.mock({
        'list|10-100':1,
    })
}
//使用delay方法可以延遲返回數(shù)據(jù)
module.exports=delay(data,1000);

修改package.json文件,在scripts下新增一行:

"mock": "mocker ./mock"

運行
npm mock
返回

> wos@1.0.0 mock D:\study\taro\wos
> mocker ./mock

> Server Listening at Local: http://localhost:3721/
>           On Your Network: http://192.168.60.1:3721/

我們可以開始在瀏覽器中測試攀痊,輸入 http://localhost:3721/api/user桐腌,1秒后返回mock數(shù)據(jù):

{"id":1,"username":"kenny","sex":6}

輸入 http://localhost:3721/api/hi/name='tony',1秒后返回mock數(shù)據(jù):

{"id":1,"username":"tony"}

3. 完善代碼

我們設(shè)計的App分為四個模塊:

  • 首頁
  • 分析模塊
  • 營銷模塊
  • 賬戶模塊
  • 登陸模塊
    我們使用之前增加的模板功能,增加另外的3個頁面:
npm run g home
npm run g analysis
npm run g account
npm run g login

3.1微信小程序首頁

現(xiàn)在我們開始修改app.js文件:

import '@tarojs/async-await'
import Taro, { Component } from '@tarojs/taro'
import { Provider } from '@tarojs/redux'
import Index from './pages/index'
import './app.less'

-import configStore from './store'
+import dvaApp from './util/dva'
+import models from './models'

// 如果需要在 h5 環(huán)境中開啟 React Devtools
// 取消以下注釋:
// if (process.env.NODE_ENV !== 'production' && process.env.TARO_ENV === 'h5')  {
//   require('nerv-devtools')
// }

+const app=dvaApp.createApp({models})
+const store=app.getStore()
-const store = configStore()

class App extends Component {

  config = {
    //頁面路由表
    pages: [
+    'pages/home/index',
+    'pages/analysis/index',
+    'pages/account/index',
+    'pages/market/index'
+    'pages/login/index'
    ],
    window: {
      backgroundTextStyle: 'light',
      navigationBarBackgroundColor: '#fff',
      navigationBarTitleText: 'WeChat',
      navigationBarTextStyle: 'black'
    },
    //底部工具欄
    tabBar: {
+      list: [{
+        pagePath: "pages/home/index",
+        text: "首頁",
+        iconPath: "./images/tab/home.png",
+        selectedIconPath: "./images/tab/home-active.png"
+     }, {
+        pagePath: "pages/analysis/index",
+       text: "分析",
+        iconPath: "./images/tab/cart.png",
+       selectedIconPath: "./images/tab/cart-active.png"
+      }, {
+        pagePath: "pages/market/index",
+        text: "營銷",
+        iconPath: "./images/tab/cart.png",
+        selectedIconPath: "./images/tab/cart-active.png"
+      },{
+        pagePath: "pages/account/index",
+        text: "賬戶",
+        iconPath: "./images/tab/user.png",
+        selectedIconPath: "./images/tab/user-active.png"
+      }],
+    }
+  }

  componentDidMount () {}

  componentDidShow () {}

  componentDidHide () {}

  componentCatchError () {}

  componentDidCatchError () {}

  // 在 App 類中的 render() 函數(shù)沒有實際作用
  // 請勿修改此函數(shù)
  render () {
    return (
      <Provider store={store}>
        <Index />
      </Provider>
    )
  }
}

Taro.render(<App />, document.getElementById('app'))

再次編譯運行微信小程序,這次,你將看到微信小程序底部出現(xiàn)了4個圖標(biāo).點擊圖標(biāo)后頁面進(jìn)行了正確的跳轉(zhuǎn).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市苟径,隨后出現(xiàn)的幾起案子案站,更是在濱河造成了極大的恐慌,老刑警劉巖棘街,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蟆盐,死亡現(xiàn)場離奇詭異,居然都是意外死亡遭殉,警方通過查閱死者的電腦和手機(jī)石挂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來险污,“玉大人痹愚,你說我怎么就攤上這事。” “怎么了拯腮?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵窖式,是天一觀的道長。 經(jīng)常有香客問我疾瓮,道長脖镀,這世上最難降的妖魔是什么飒箭? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任狼电,我火速辦了婚禮,結(jié)果婚禮上弦蹂,老公的妹妹穿的比我還像新娘肩碟。我一直安慰自己,他們只是感情好凸椿,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布削祈。 她就那樣靜靜地躺著,像睡著了一般脑漫。 火紅的嫁衣襯著肌膚如雪髓抑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天优幸,我揣著相機(jī)與錄音吨拍,去河邊找鬼。 笑死网杆,一個胖子當(dāng)著我的面吹牛羹饰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播碳却,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼队秩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昼浦?” 一聲冷哼從身側(cè)響起馍资,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎关噪,沒想到半個月后迷帜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡色洞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年戏锹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片火诸。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡锦针,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奈搜,我是刑警寧澤悉盆,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站馋吗,受9級特大地震影響焕盟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宏粤,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一脚翘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绍哎,春花似錦来农、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至海诲,卻和暖如春繁莹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背特幔。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工咨演, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人敬辣。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓雪标,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溉跃。 傳聞我的和親對象是個殘疾皇子村刨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.1、 分配更多資源 1.1.1撰茎、分配哪些資源嵌牺? Executor的數(shù)量 每個Executor所能分配的CPU數(shù)...
    miss幸運閱讀 3,184評論 3 15
  • YarnYarn產(chǎn)生背景:Yarn直接來自于MR1.0MR1.0 問題:采用的是master slave結(jié)構(gòu),ma...
    時待吾閱讀 5,673評論 2 23
  • 最近寫一個微信小程序的項目龄糊,由于是協(xié)同開發(fā)逆粹,前期的搭建工作由另一個妹子完成,現(xiàn)在項目階段一完成了炫惩,為了備忘回顧僻弹,做...
    陳小生_1017閱讀 24,437評論 17 42
  • 時間過得很快,快到在彼此轉(zhuǎn)身之際他嚷,卻已經(jīng)相隔天涯蹋绽。 你是我忘不了的想念芭毙,也是我回不去的從前。生日那天來了一個未知的...
    大喵彌彌閱讀 288評論 0 0
  • 昨天,你去看七微的主題分享見面會蚣抗,七微借傅清時對霓喃的話侈百,在書中寫道,“在海洋面前翰铡,你只能讓自己融入钝域,去適應(yīng)它的一...
    立黃昏閱讀 1,515評論 59 42