和taro一起做SPA 2.技術(shù)??蚣?react

從這章開始,我們開始逐漸講解相關(guān)的技術(shù)棧框架,讓我們先從react開始

1.第一個(gè)react程序

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

npm install -g create-react-app

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

mkdir basic
create-react-app basic

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

cd basic
yarn start

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

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

其中/public/index.html就是我們的頁(yè)面模板,所有的組件都會(huì)渲染在這個(gè)文件上.
讓我們看一下第一個(gè)組件:/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();

這個(gè)組件很簡(jiǎn)單,重要的是這一句:

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

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

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ā)一個(gè)計(jì)數(shù)器程序來(lái)學(xué)習(xí)一下React開發(fā).首先,讓我們自己定義一個(gè)Counter組件.
首先,我們?cè)趕rc根目錄下新建一個(gè)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();

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

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

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

讓我們看接下來(lái)的渲染部分的處理:

    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方法返回了一個(gè)jxs的代碼段.所謂的jxs,簡(jiǎn)單說(shuō)就是嵌入了react語(yǔ)句的html代碼段.
在這里,你特別需要注意的是,React會(huì)自動(dòng)跟蹤state值的變化進(jìn)行渲染,因此,你不需要像傳統(tǒng)開發(fā)一樣手動(dòng)渲染數(shù)據(jù),只需要簡(jiǎn)單的標(biāo)明會(huì)發(fā)成變更的數(shù)據(jù)即可:

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

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

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

下面是HTML的寫法:

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

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

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

代碼很簡(jiǎn)單,但是有幾個(gè)需要注意的地方:

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

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

export default Counter

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

2.3.2 React組件間通訊

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

import React,{Component } from "react";
import Counter from "./counter"
class counterControl extends Component{
    constructor(){
        super();
        //設(shè)置組件的初始值
        this.state={value:0}
    }
    //這個(gè)地方需要特別注意,change是Counter組件每次點(diǎn)擊發(fā)生變化的值
    change(change){
        //不可以修改state的原始值
        let value=this.state.value;
        value+=change;
        //必須通過(guò)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獨(dú)立,因此,組件之間的通訊就落到了change方法里.讓我們看一下change方法的實(shí)現(xiàn):

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

change方法實(shí)際是CounterControl通過(guò)屬性傳遞給Counter子組件的回調(diào)函數(shù).他的實(shí)現(xiàn)原理是每次Counter組件被點(diǎn)擊時(shí),把Counter組件state值的變化回調(diào)至CounterControl,從而實(shí)現(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

重點(diǎn)看一下組件的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);
    }

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

2.3.3 優(yōu)化

上面的例子我們發(fā)現(xiàn),Counter組件如果需要整合到CounerControl組件中,就必須進(jìn)行修改.
原來(lái)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);
    }

也就是說(shuō),Counter組件并不是一個(gè)通用的組件.造成這種情況的原因,是由于Counter組件增加了過(guò)多的業(yè)務(wù)邏輯.
如果把組件的顯示和業(yè)務(wù)邏輯進(jìn)行剝離成內(nèi)部組件和容器組件,就可以解決這個(gè)問(wèn)題.內(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的組件稱之為無(wú)狀態(tài)組件.無(wú)狀態(tài)組件可以進(jìn)一步簡(jiǎ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>
    )
}

無(wú)狀態(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;

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

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

import React,{Component} from 'react';
import NgCounter from './ngCounter'
//內(nèi)部無(wú)狀態(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總結(jié)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容