taro

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

2019-01-09  本文已影响0人  shtonyteng

从这章开始,我们开始逐渐讲解相关的技术栈框架,让我们先从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代码构成.
这个代码段有几个特别需要注意的地方:

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

下面是HTML的写法:

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

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

  <button onClick={this.add.bind(this)}>+</button>
    desc(){
        let value=this.state.value-1;
        this.setState({value})
    }
    add(){
        let value=this.state.value+1;
        this.setState({value})
    }

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

  let value=this.state.value+1
  this.state.value=value
   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属性传递回调方法层层调用.因此,对于多个组件协调工作时,这种实现方法就显得很笨拙而且效率低下.

上一篇下一篇

猜你喜欢

热点阅读