美文网首页taro
和taro一起做SPA 2.技术栈框架:react

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

作者: shtonyteng | 来源:发表于2019-01-09 12:39 被阅读0次

    从这章开始,我们开始逐渐讲解相关的技术栈框架,让我们先从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属性传递回调方法层层调用.因此,对于多个组件协调工作时,这种实现方法就显得很笨拙而且效率低下.

    相关文章

      网友评论

        本文标题:和taro一起做SPA 2.技术栈框架:react

        本文链接:https://www.haomeiwen.com/subject/mowwrqtx.html