和taro一起做SPA 2.技术栈框架:react

从这章开始,我们开始逐渐讲解相关的技术栈框架,让我们先从react开始

1.第一个react程序

由于react需要依赖大量的依赖库,如通过babel对es6的转化,css的渲染…对于新手来说,可能这一大堆配置就让人望而却步。为了简化开发环境的搭建,让我们直接用create-react-app 搭建脚手架(具体命令含义可以先不用理解):
首先,我们安装create-react-app

npm install -g create-react-app

安装成功后,就可以开始使用

mkdir basic
create-react-app basic

basic是我们第一个react程序的名称,create-react-app命令会运行一段时间,帮我们搭建脚手架和开发环境.命令执行后,我们开始启动我们的应用:

cd basic
yarn start

yarn start命令后,webpack会启动webpack-dev-server跟踪我们代码修改,然后自动启动一个端口为3000的HttpServer帮我们进行调试,并且很贴心的弹出URL为http://localhost:3000的浏览器页面,显示我们的第一个react应用.
好,让我们开始学习第一个react程序,首先,让我们看一下脚手架帮我们搭建的目录结构。

,create-react-app创建的目录结构

其中/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组件,系统如何区分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"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}
export default App;

好,现在让我们开发一个计数器程序来学习一下React开发.首先,让我们自己定义一个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})
    }
} 
//将组件导出模块
export default Counter

然后,修改index.js文件,渲染我们新开发的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();

页面会自动刷新修改后的内容,(是不是很惊讶)显示结果.点击 + 和 - 按钮系统会自动显示最新的counter.
让我们来分析一下代码:

首先 ,我们的Counter组件继承自React.Component.我们的Counter组件继承Component组件,并且实现了构建器.在构建器中,完成了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组件的核心部分,主要完成组件的表现.在这里,render方法返回了一个jxs的代码段.所谓的jxs,简单说就是嵌入了react语句的html代码段.
在这里,你特别需要注意的是,React会自动跟踪state值的变化进行渲染,因此,你不需要像传统开发一样手动渲染数据,只需要简单的标明会发成变更的数据即可:

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

在这里,{}表示的是里面的部分是由react代码构成.
这个代码段有几个特别需要注意的地方:

  • return语句直接返回JSX时,必须用()进行包裹,下面的语句由于<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事件命名机制不同,让我们对比一下React的写法:
  <button onClick={this.add.bind(this)}>+</button>

下面是HTML的写法:

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

有三个重要的区别:
1.React事件触发是骆驼命名方式.而HTML的触发方式是全部小写;
2.React事件触发是函数名,而HTML的触发方式是函数执行代码块;
3.React事件处理函数this指针不会绑定任何对象,而HTML指针会自动绑定到window对象;

  • 如果处理state,则React事件处理函数需要绑定this指针到组件
    这就是以下代码的原因,通过bind方法将函数this指针绑定到React Component:
  <button onClick={this.add.bind(this)}>+</button>
  • state的处理原则
    让我们看一下事件处理方法的实现:
    desc(){
        let value=this.state.value-1;
        this.setState({value})
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
    }

代码很简单,但是有几个需要注意的地方:

  • 不能直接修改state的值,必须通过this.setState方法修改,否则,系统不会进行渲染.
  let value=this.state.value+1
  this.state.value=value
  • state的原始值不能修改,因此,以下代码是无效的:
   let value=this.state.value++; //this.state.value++修改了原始的state的值
   this.setState({value})

第一个react程序结束了,React的逻辑很简单,每个组件都有一个state值,组件通过监控state的状态变化实现页面渲染.
最后,别忘了需要从模块中导出我们的组件:

export default Counter

导出是,如果不增加default参数,导入时需要将组件名称用{}括起来.一个模块中智能有唯一的一个default组件.

2.3.2 React组件间通讯

从第一个例子我们可以发现,React组件开发很容易,通过监控组件state值的变化,实现自动的渲染,极大的减轻了开发的工作量.但是每个组件都有自己的state,如果多个组件需要通讯,问题就变得复杂了.
让我们看下面的这个例子,在这个例子,组件Control由3个Counter构成,每个Counter都可以自动增减,CounterControl显示的值是3个Counter的累加值.
这个例子显示了组件间如何进行通信.
先让我们看一下CounterControl组件的代码:

import React,{Component } from "react";
import Counter from "./counter"
class counterControl extends Component{
    constructor(){
        super();
        //设置组件的初始值
        this.state={value:0}
    }
    //这个地方需要特别注意,change是Counter组件每次点击发生变化的值
    change(change){
        //不可以修改state的原始值
        let value=this.state.value;
        value+=change;
        //必须通过this.setState方法进行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组件也发生了变化,增加了name属性和change方法.

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

由于组件彼此state独立,因此,组件之间的通讯就落到了change方法里.让我们看一下change方法的实现:

    //这个地方需要特别注意,change是Counter组件每次点击发生变化的值
    change(change){
        //不可以修改state的原始值
        let value=this.state.value;
        value+=change;
        //必须通过this.setState方法进行state的修改
        this.setState({value:value})
    }

change方法实际是CounterControl通过属性传递给Counter子组件的回调函数.他的实现原理是每次Counter组件被点击时,把Counter组件state值的变化回调至CounterControl,从而实现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变更的同时,都需要调用通过props属性传递的change方法回调CounterControl提供的chanage方法,实现state变化的通知.

2.3.3 优化

上面的例子我们发现,Counter组件如果需要整合到CounerControl组件中,就必须进行修改.
原来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的回调方法
        this.props.change(-1)
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
        //增加了change的回调方法
        this.props.change(1);
    }

也就是说,Counter组件并不是一个通用的组件.造成这种情况的原因,是由于Counter组件增加了过多的业务逻辑.
如果把组件的显示和业务逻辑进行剥离成内部组件和容器组件,就可以解决这个问题.内部组件只负责显示,容器组件则负责具体的业务逻辑和state处理.让我们看一下如何进行剥离:
新的NgCounter内部组件代码如下:

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组件不再进行任何业务逻辑的处理,也不处理state相关的数据.它只是按照传递的属性值进行显示或回调.
我们管这种不处理任何state的组件称之为无状态组件.无状态组件可以进一步简化为函数,如下所示:

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>
    )
}

无状态组件函数由于没有this指针,属性props由容器组件传递.
让我们看一下容器组件如何进行处理

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;

通过内部组件和容器组件的拆分,如果组件需要嵌入其他组件,则只需要修改容器组件即可.内部组件不需要进行任何调整.
需要特别注意的是,此时导出的组件为容器组件.

我们把CounterControl也进行了改造,相应的代码如下:

import React,{Component} from 'react';
import NgCounter from './ngCounter'
//内部无状态组件退化为函数
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>
    )
}
//容器组件负责具体的业务逻辑和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 (
            //返回内部组件
            <NgCounterControl add={this.add.bind(this)} 
            sub={this.sub.bind(this)}
            value={this.state.value} />
        )
    }
}
//导出容器组件
export default Container;

2总结

通过上面的例子,我们可以发现,虽然我们把组件拆分为内部组件和容器组件,实现了内部组件的独立性,但是,由于React组件彼此都维护自己的state,当多个组件需要同步state值的时候,情况还是变得很复杂,组件必须通过props属性传递回调方法层层调用.因此,对于多个组件协调工作时,这种实现方法就显得很笨拙而且效率低下.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,783评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,396评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,834评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,036评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,035评论 5 362
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,242评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,727评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,376评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,415评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,463评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,140评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,734评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,809评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,028评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,521评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,119评论 2 341

推荐阅读更多精彩内容