刘贵学博客

Electron 0x01. 开发环境搭建

1. Electron 安装

1.1 node.js 安装

下载地址:

https://nodejs.org/en/download/current/

版本选择最新版即可。

npm install -g npm

1.2 打包工具

开发完成后,需要打包,安装electron-packager工具

npm install -g electron-package

使用方法参考 https://github.com/electron-userland/electron-packager

2. Hello World

git clone https://github.com/electron/electron-quick-start
electron .

可查看运行效果。

3. 代码结构分析

TBD.

Weex 入门 3 主页面修改

大多数主页面都是 基于 tabbar来制作的。

https://alibaba.github.io/weex-ui/#/cn/packages/wxc-tab-bar/

接下来所有的基础UI 除了weex本身的,还会引入 weex-ui

1. 安装 weex-ui

npm install --save-dev weex-ui

参考 https://alibaba.github.io/weex-ui/#/cn/packages/wxc-tab-bar/

2. index.vue

<template>
<div>
  <wxc-minibar title="title"
    background-color="#009ff0"
                  :use-default-return   = "false"
                   text-color="#FFFFFF"
                   right-text="more">
  </wxc-minibar>

  <wxc-tab-bar :tab-titles="tabTitles"
               :tab-styles="tabStyles"
               title-type="icon"
               :tab-page-height="tabPageHeight"
               @wxcTabBarCurrentTabSelected="wxcTabBarCurrentTabSelected">
     <!-- 第一个页面内容-->
    <div class="item-container" :style="contentStyle"><text>首页</text></div>
    <!-- 第二个页面内容-->
    <div class="item-container" :style="contentStyle"><text>特别推荐</text></div>
    <!-- 第三个页面内容-->
    <div class="item-container" :style="contentStyle"><text>消息中心</text></div>
    <!-- 第四个页面内容-->
    <div class="item-container" :style="contentStyle"><text>我的主页</text></div>
  </wxc-tab-bar>
</div>
  
</template>

<style scoped>
  .item-container {
    width: 750px;
    background-color: #f2f3f4;
    align-items: center;
    justify-content: center;
  }
</style>
<script>
  import { WxcTabBar, WxcMinibar, Utils } from 'weex-ui';
  import Config from './config'

  export default {
    components: { WxcTabBar, WxcMinibar},
    data: () => ({
      tabTitles: Config.tabTitles,
      tabStyles: Config.tabStyles
    }),
    created () {
      this.tabPageHeight = Utils.env.getPageHeight();
      const { tabPageHeight, tabStyles } = this;
      this.contentStyle = { height: (tabPageHeight - tabStyles.height) + 'px' };
    },
    methods: {
      wxcTabBarCurrentTabSelected (e) {
        const index = e.page;
      }
    },
    mounted(){
      console.log("tabPageHeight=", this.tabPageHeight);
    }
  };
</script>

3. config.js


export default {
    
      tabTitles: [
        {
          title: '首页',
          icon: 'https://gw.alicdn.com/tfs/TB1MWXdSpXXXXcmXXXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB1kCk2SXXXXXXFXFXXXXXXXXXX-72-72.png',
        },
        {
          title: '特别推荐',
          icon: 'https://gw.alicdn.com/tfs/TB1ARoKSXXXXXc9XVXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB19Z72SXXXXXamXFXXXXXXXXXX-72-72.png'
        },
        {
          title: '消息中心',
          icon: 'https://gw.alicdn.com/tfs/TB1VKMISXXXXXbyaXXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB1aTgZSXXXXXazXFXXXXXXXXXX-72-72.png',
          badge: 5
        },
        {
          title: '我的主页',
          icon: 'https://gw.alicdn.com/tfs/TB1Do3tSXXXXXXMaFXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB1LiNhSpXXXXaWXXXXXXXXXXXX-72-72.png',
          dot: true
        }
      ],
      tabStyles: {
        bgColor: '#FFFFFF',
        titleColor: '#666666',
        activeTitleColor: '#3D3D3D',
        activeBgColor: '#FFFFFF',
        isActiveTitleBold: true,
        iconWidth: 70,
        iconHeight: 70,
        width: 160,
        height: 120,
        fontSize: 24,
        textPaddingLeft: 10,
        textPaddingRight: 10
      }
    }

4. 执行后的预览效果

100X100

Weex 入门 2 修改Splash页面

Splash页面指的是App的首次启动页面,而常见的App界面应该只需要一个 图片即可。原有的Weex启动页,是个动画效果,动画执行完成后(固定的时间)自动加载主页面。所以,这并不是真正意义上的 等待页面。

所以,我们的需求是: 在主页面加载完成后,等待页面消失。

App的启动顺序是:

WXApplication -> SplashActivity -> WXPageActivity

  • WXApplication 初始化配置、加载插件;
  • SplashActivity App等待页
  • WXPageActivity App主页面

1. SplashActivity

功能需求:
1. 如果加载layout/activity_splash.xml, 耗时太多了。 SplashActivity的资源文件应该越简单约好。
2. 初始化加载主页功能,完成后,直接跳转到主页。

    package com.alibaba.weex;
    
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.text.TextUtils;
    import com.alibaba.weex.commons.util.AppConfig;
    
    public class SplashActivity extends AppCompatActivity {
    
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        gotoMainPage();
        finish();
    
      }
    
    
      private void gotoMainPage(){
        String url = AppConfig.getLaunchUrl();
        if (!TextUtils.isEmpty(url)) {
          Intent intent = new Intent(Intent.ACTION_VIEW);
          String scheme = Uri.parse(url).getScheme();
          StringBuilder builder = new StringBuilder();
          if (TextUtils.equals("file", scheme)) {
            intent.putExtra("isLocal", true);
          } else if (!TextUtils.equals("http", scheme) && !TextUtils.equals("https", scheme)) {
            builder.append("http:");
          }
          builder.append(url);
    
          Uri uri = Uri.parse(builder.toString());
          intent.setData(uri);
          intent.addCategory("com.taobao.android.intent.category.WEEX");
          intent.setPackage(getPackageName());
          startActivity(intent);
          finish();
        }
      }
    }

2. AndroidManifest.xml

2.1 设置 SplashActivity 的 theme 为SplashTheme

 <activity
                android:name=".SplashActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:label="@string/app_name"
                android:screenOrientation="portrait"
                android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

2.2 在 res/values/theme.xml 目录下:

 <style name="SplashTheme" parent="FullscreenTheme">
    <item name="android:windowBackground">@drawable/splash_page</item>
    <item name="android:windowFullscreen">true</item>
  </style>

2.3 在 res/drawable/splash_page.xml 目录下:

 <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@color/colorWhite" />

    <item>
        <bitmap
            android:gravity="fill"
            android:src="@drawable/splash_pic" />
    </item>

</layer-list>

把等待页面移到 res/drawable/splash_pic.png 即可。

Weex 入门 1 环境安装

1. 安装

weexpack 是面向整个weex项目的管理工具,容易跟 weex init混淆,weex init 适用更简单的情景。

npm install -g weexpack

创建 Weex Hello例子:

weexpack create helloWeex
cd helloWeex
npm install
weexpack platform add android

2. 运行程序

2.1 在 web 端运行

npm run build
npm run serve

2.2 在 android 端运行

使用USB 链接Android手机后,执行命令:

weex run android

3. 用 Android Studio 3.0 打开 weex项目

3.1 坑1: 使用 AS 3.0 提示 从 Gradle 3.0 升级 Gradle 4.1 后,会有一个错误:

Error:(24, 0) Cannot set the value of read-only property ‘outputFile’ for ApkVariantOutputImpl_Decorated{apkData=Main{type=MAIN, fullName=debug, filters=[]}} of type com.android.build.gradle.internal.api.ApkVariantOutputImpl.
Open File

解决方案:
把 build.gradle 的

applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def outputFile = output.outputFile
        if (outputFile != null && outputFile.name.equals('app-debug.apk')) {
            def fileName = outputFile.name.replace("app-debug.apk", "playground.apk")
            output.outputFile = new File(outputFile.parent, fileName)
        }
    }
}

修改为:

static def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

android.applicationVariants.all { variant ->
    variant.outputs.all { output ->
        def outputFile = output.outputFile
        if (outputFile != null && outputFile.name.endsWith('.apk')) {
            def fileName = outputFile.name.replace("app", 
                    "${defaultConfig.applicationId}_${defaultConfig.versionName}_${releaseTime()}")
            outputFileName = fileName
        }
    }
}

}

3.3 坑3:

执行 weex run android 时,提示找不到 gradle 3,

3.2 坑2:没有找到 android-23

Error:Missing Android platform(s) detected: 'android-23'
Install missing platform(s) and sync project

因为在AS3.0 下默认使用的 是 SDK 26

compileSdkVersion 26
buildToolsVersion '26.0.2'

compile 'com.android.support:support-v4:26.0.2'
compile 'com.android.support:appcompat-v7:26.0.2'

3.3 替换App ID, App Logo 与 App Name

  1. App Id 的修改在 platforms/android/app/build.gradle

  1. 将不同清晰度的Logo图片,命名为ic_launcher.png 替换到 platforms/android/app/src/main/res,下对应的目录里:

    mipmap-mdpi     160dpi
    mipmap-hdpi     240dpi
    mipmap-xhdpi    320dpi
    mipmap-xxhdpi   480dpi
    mipmap-xxhdpi   720dpi
    
  2. 修改App名称

修改 platforms/android/app/src/main/res/values/strings.xml (也有可能是 values-zh-rCN/strings.xml) app_name 标记。

Vue 中的疑难杂症整理

1. 数组不支持监听

1). $set 元素赋值

    this.arr[2] =  "modify"

替换:

    this.$set(this.arr,  2,  "modify");

2). 使用 splice也可替换

    this.arr.splice(2, 1, "modify")

2. 打包Cordova资源加载问题

注册一个全局 $static 函数,用于绝对路径的转换:

Vue.prototype.$static = function(path) {
        let isCordova = window.location.protocol.match(/^file/);

    let staticPath = path;
    if(path.match( /^\/static/) ){
      if(isCordova){
        staticPath = "/android_asset/www/"+path;
      }
    }
    console.log("staticPath=",staticPath);
    return staticPath;
}

使用方法:

  computed: {
        thumbnail(){
          return this.$static(this.project.Thumbnail);
        }
  },

背景的使用方法:

<div class="home-page" :style="bgStyle">


computed:{
       bgStyle(){
           return {
             backgroundImage: `url(${this.$static("/static/index.png")})`
          }
       }
  },

3. Cordova 编译apk

参考: https://7449.github.io/2017/08/07/Android_Cordova_apk/

Vue 用户管理

  • 关于注册;
  • 关于登陆;
  • 如何保持session
  • 如何验证接口的安全性;
  • 如何控制测试账号

Vue

5. 如何控制测试账号

方案一: 给Email 账户添加后缀,比如 liuguxiue@qq.com,范式注册未 liuguixue@qq.com.test 这种默认都过。

方案二: 后台用户审核,加入允许测试的用户列表

夯实Vue系列 Demo 10 综合实例 TodoList(数据库)

至此,我们的例子已经查不到了,最后我们再完成一个综合实例,将 Demo 6 结合 Demo 9,完成一个带数据库的TodoList。

代码分析(获取初始数据)

1. 整体流程

2. 组件监听items更新,并且调用 InitTodoItem Action

  • items 设为计算属性,如果变更自动 更新;
  • mounted 函数里 dispatch 一个 InitTodoItem

3. Action 里的逻辑

  • Action里执行 Axios 的get函数;
  • Axios.get成功后,将获取数据使用 commit 传入 mutation 的 initTodoItems;

运行结果

其他的动作 增加、删除、更新 的逻辑 请参考:

https://coding.net/u/guixue/p/vue-starter/git/tree/master/src/pages/10-todo-db

夯实Vue系列 Demo 9 数据库:MongoDB

安装

1. Mac下安装

brew install mongodb
mongod --config /usr/local/etc/mongod.conf

2. Windows 下安装

注意:记得 D:/mongo/db 要存在,数据库文件都存储在这里。

常用工具

mongo 客户端常用命令

 show dbs           //显示数据库;
 use test               //使用某个数据库
 db.test.insert({‘name’:’byc’}) //插入一条记录
 db.test.find()         //查找所有记录
 db.test.findone()  //查找一条记录
 db.dropDatabase() //删除数据库

RoboMongo

https://robomongo.org/

Mongoose 的API

nodejs 中使用 mongoDB 用的是 Mongoose 库安装方法:

npm install mongoose --save-dev

1. Connect

connect(“mongodb://127.0.0.1:27017/yourdb");

2. Schema

 mongoose.Schema({XXXX})

3. model 模型

mongoose.model("Todo", TodoSchema)  //设置模型
mongoose.model("Todo")  //获取模型

4. 增、删、改、查

 Model.save()
 Model.remove()
 Model.update()
 Model.find()

代码分析

后台相关代码:

1. build/dev-server.js

这里会引入 /db/todo 的rest 服务调用入口:

2. server/config.js, mongoose.js

这里对 mongoose的 配置文件,在 build/dev-server.js 里盗用

3. model的定义与使用

  • model 的定义,/server/todo/model.js

  • model 的使用 /server/todo/api.js

结合客户端的测试效果

本例所有源码,请参考:
https://coding.net/u/guixue/p/vue-starter/git/commit/f49274908f4be37b

夯实Vue系列 Demo 8 RESTful Client:Axios

Demo 7 实现了一个简单的 RESTful 服务器,我们测试接口使用的是 Postman工具,实际代码中,我们也需要实现 客户端工具,Vue中有个 vue-resources 库,但大牛们已经建议使用 Axios。

Axios介绍

Axios 优势:

其中 Promise API 指的是什么?

如下:

  axios.get('/api/todo/2')
      .then(  function (response) { rest.ok(response); } )
      .catch( function (error)   { rest.err(error);  }   );

来替换 万恶的 callback 大坑。

代码实现

1. 配置 axios 的默认值:

其中 第 6行,将接口的header默认类型定位: json格式, 我们Demo 7中Rest服务器中需要补充 对 json格式的支持:

2. 编写通用接口服务:

3. 在nodejs 中测试服务

执行 ./nodejs.js

参考代码:
https://coding.net/u/guixue/p/vue-starter/git/blob/master/src/pages/8-rest-client/nodejs.js

4. 在 浏览器中

参考代码:

https://coding.net/u/guixue/p/vue-starter/git/tree/master/src/pages/8-rest-client

夯实Vue系列 Demo 7 RESTful Server

从Demo1 到 Demo6 主要讲了使用Vue构建Web程序的前端实现方法,后端没有任何涉及。

从本例开始,我们将介绍 现代web应用程序的 后端编程方法。

API 接口方式

目前比较常用的API 接口方式有三种:

  • SOAP
  • JSON-RPC
  • RESTful

    三种协议各有优势,但 RESTful 更简单通用,本例将采用 Express 来实现一个 RESTful 服务程序。

    关于RESTful详情介绍本文将不再展开,请参考: RESTful 架构详解

环境安装

Express 中有个模块 express-rest :

npm install --save-dev express-rest

express-rest 使用方法 详见: npm express-rest

实现

1. 增加rest功能到 Express

/build/dev-server.js 中已经新建了一个 Express的实例app,我们只需要把 rest服务增加给app即可。

2. restful-server.js 代码架构

3. get 实例

  rest.get('/api/todo', function(req, rest) {
    rest.ok(records);
  });

使用 Postman工具 测试 服务

代码参考

本文中的所有代码,请参考:

https://coding.net/u/guixue/p/vue-starter/git/commit/b12c0ed4936427073fd9a

夯实Vue系列 Demo 6 综合实例 TodoList(Vuex 实现)

上文Demo5里已经实现了 todolist,功能如下:

  • 新增:在输入框中输入内容,回车可新增;
  • 修改:单击条目可设置为完成;
  • 删除:双击条目可删除;

Demo5里的 DateStore.js 是利用浏览器的localStorage保存数据的,Vue中有专门的组件来处理数据存储: Vuex。

本例将使用Vuex来处理 TodoList里的 存储功能。

Vuex 简介

Vuex 里的一些核心概念:

这里有一个经典的概念流程的图,如下:

  1. Vue 的组件是通过数据 【state】来渲染的, 从Store中获取指定的 State:

    • Getter 的调用方法如下:

    • Getter 实现如下:

    todoItems: (state) => {
    return state.todoItems
    }
    
  2. Vue 的组件的修改会设置 State 流程如下:

    • 组件通过 store.dispatch 将设置 传给 Action

    • Action 做一些处理之后,使用 commit 方法,调用 mutation,调用方法如下:

    • mutation 修改State存入 Store, 实现方法如下:

    setTodoItems(state, todoItems) {
    state.todoItems = todoItems
    }
    

运行效果

本文中的所有代码,请参考:
https://coding.net/u/guixue/p/vue-starter/git/commit/84e6a3127e5a1a4dd4

夯实Vue系列 Demo **3 父子组件双向通讯:V-model

我们在Demo1和 Demo2 分别介绍了父子间的通讯原理:
* 父 -> 子:Props
* 子 -> 父:Emit

本例将二者综合在一起,实现双向通讯,也就是其他框架所说的【双向绑定】,当然通讯原理还是和我们上面 Demo1 和 Demo2一致的。

业务场景

我们需要实现一个 折叠面板,如下图所示:

  1. 如不勾选,此模块的详情将隐藏;
  2. 如勾选,此模块的详情将显示;
  3. 每个模块的勾选状态需要父组件传入,并允许更改;

实现(父->子)

代码解析:

  1. 左侧是 父组件(Parent),两次调用 Panel 展示 模块A 与模块B
  2. 红色圈中是 Panel中 添加一个 isShow的属性;
  3. Parent中分别 将变量 showA, showB 传入 Panel

已经能实现【业务场景】中的所有效果,但是,当切换勾选时,浏览器会抛出警告:

意思是,不要在子组件内修改 父组件传入的props。

那我们修改一下:

实现(子->父)

代码解析:

  1. 右侧的 子组件 Panel 新加了 一个计算属性my_show,当子组件触发其修改的时候,会调用 set 函数,$emit 一个input事件给父组件;

注: 为了方便,Panel组件的 isshow 属性 修改为 value了。

  1. 父组件内的 Panel A 与 B 分别使用了两种方式来接受 input 事件。其实v-model只是一个语法糖,将input事件固定关联 value属性的修改:

v-model是否可以绑定多个元素呢?

可以的,传入对象即可,如下:

1. Parent.vue

2. 子组件 Panel.vue

检测到obj内的成员变化,发送input事件给父组件。

代码实现

参考 https://coding.net/u/guixue/p/vue-starter/git

![](media/14952939575744/14957046058782.jpg)

夯实Vue系列 Demo 5 综合示例:TodoList

这里我们给出一个简单的 Todo List使用示例,提供的操作如下:

  • 新增:在输入框中输入内容,回车可新增;
  • 修改:单击条目可设置为完成;
  • 删除:双击条目可删除;

代码实现

1. index.vue 模板

模板里用3个事件来实现基本需求:

  • addItem 新增,在输入框中输入内容,回车可新增
  • toggleFinish,修改,单击条目可设置为完成;
  • deleteItem, 删除,双击条目可删除;

    <template>
    <div id="todo-app">
    <h1 v-text="title"></h1>
    
    <p class="note" >操作提醒:
      <br />* 新增:在输入框中输入内容,回车可新增
      <br />* 修改:单击条目可设置为完成;
      <br />* 删除:双击条目可删除;
      <br /> <br />
    </p>
    
    <input type="text" v-model="newItem" v-on:keyup.enter="addItem">
    
    <ul>
      <li v-for="item in items" v-bind:class="{'cls-finished': item.isFinished}"
          @click="toggleFinish(item)"  @dblclick="deleteItem(item)">
        {{ item.label }}
      </li>
    </ul>
    
    </div>
    </template>
    
    

    2. index.vue 脚本

  • methods实现3个事件: addItem、toggleFinish & deleteItem;

  • items 数据的管理:

    • DataStore.fetch() 获取当前所有的记录;
    • 在watch里,如果 items任何改变,将调用 DataStore.save(items) 保存数据
    <script>
    import DataStore from './DataStore.js'
    
    Array.prototype.remove = function(val) {
    var index = this.indexOf(val);
    if (index > -1) {
      this.splice(index, 1);
    }
    };
    
    export default {
    data(){
      return {
        title:"Todo List",
        items:  DataStore.fetch(),
        newItem:""
      }
    },
    methods: {
      toggleFinish(item){
        item.isFinished = !item.isFinished;
      },
      addItem(){
        this.items.push({label:this.newItem, isFinished:false});
        this.newItem  = "";
      },
    
      deleteItem(item){
        this.items.remove(item);
      }
    },
    
    watch:{
      items : {
        handler: function (items) {
          DataStore.save(items);
        },
        deep: true
      }
    }
    }
    </script>
    
    

3. DataStore.js

fetch 和 save的实现:

  • fetch, 从本地存储(localStorage)中获取记录;
  • save, 将记录保存到localStorage里;

DataStore相当于一个本地数据库。

const ITEMS_KEY ='todo_items';

export default {
  fetch(){
    return JSON.parse(
      window.localStorage.getItem(ITEMS_KEY) ||'[]')
  },

  save(items){
    window.localStorage.setItem(
      ITEMS_KEY,JSON.stringify(items));
  }
}

运行效果

本文中的所有代码,请参考:
https://coding.net/u/guixue/p/vue-starter/git/commit/8490ab2afe5443badc

注: 本例参考自视频教程: vue.js入门基础 推荐各位观看。

夯实Vue系列 Demo 4 组件通讯:Global Bus

上文中的 Demo1、Demo2 与 Demo3 分别讲述了 父子组件通讯方法:
* 父 -> 子,属性;
* 子 -> 父,事件;
* 双向绑定, v-model

那普通组件的通讯是以什么方式进行的呢?

其实跟 Emit方式类似,只是我们用了一个全局Vue对象最为事件Bus来传递和接受消息。

示例代码如下:

Demo3 代码

组件通讯:Global Bus

源码可下载:

https://coding.net/u/guixue/p/vue-starter/git/commit/b19c249b927b393ef73de7

运行结果

夯实Vue系列 Demo 1 父子组件通讯:Props

Vue 组件通讯概述

父子组件

  • 父 -> 子:属性,即:父组件传值给子组件使用Props属性;【本文将介绍】
  • 子 -> 父,事件;子组件 传值给父组件使用Emit事件;【Demo2 介绍】

普通组件

邻居组件或兄弟组件如何传值?

两种方式
* Global Event Bus 【Demo4 会介绍】
* Vuex 【Demo6 会介绍】

Demo1 代码:

父子组件通讯:Props 实现如下:

实例代码详情可参考

https://coding.net/u/guixue/p/vue-starter/git/commit/70f792b4c0d7966c

运行结果

输入框里输入例子,子组件会收到改变的效果。

但是反过来, 子组件如果想改变 传入的属性值,浏览器会抛出 警告。

因为组件设计初衷是单向数据流,只能由父->子单向传递。

夯实Vue系列 0 环境搭建

背景

本系列是我为公司编写的内部培训资料,目标是:快速让Vue理论知识落地,2天时间培训让工程师上手干活。

我本人也是十多年的老程序员了,也非常反感 《21天入门XXX》《7天精通XXX》速食类系列: 以Demo为主的学习方法。但不得不承认,站在公司的角度,这种效果确实很好,只用2-3天左右时间,就能让工程师上手干活,当然知识体系和深度肯定不够,所以,大家切记自己要去多查手册、翻源码。

夯实Vue系列文章几点约定:

  • 系列中能用代码说明的东西,尽量不废话!

    Talk is cheap, show me code.  // 弄点英文,貌似很吊的样子
    
  • 如果涉及背景知识如果陌生,请自行查阅相关教程后再结合代码理解;

  • 每个例子的实现尽量 控制在 100行代码以内;

  • 所有代码请参考: https://coding.net/u/guixue/p/vue-starter/git 建议下载后在本地参阅;

    墙裂、墙裂建议,里面的10个例子一定要自己理解后亲自敲过!!

代码使用:

1 下载代码

git clone https://git.coding.net/guixue/vue-starter.git

例子中 使用的 vue init 新建的项目模板,命令如下:(下载的代码已经包含在内,不用再执行下面的命令了)

vue init webpack vue-starter 

2. 查看每个例子:

每个例子都已经打tag了, 链接:https://coding.net/u/guixue/p/vue-starter/git/tags

运行例子

cd vue-starter
npm install  //首次运行需要
npm run dev

浏览器将自动弹出页面 localhost:8080

参考资料:

系列教程会提供 10个经典实例代码来学习 Vue,我会假设您已经学习过Vue基础,如果没有请先学习:

如有批评建议,请发Mail给我: guixue@outlook.com

Ant Design Mobile 渐进系列教程 5:Index主页面

1. 目标

略。

2. 原型

2.1 Index页面的组件

组件结构如下图:

包含三个组件:
* TodoAdd 添加待办事;
* TodoList 展示待办事列表;
* TodoItem 展示待办事项,属于 TodoList的子组件;

2.2 Index页面功能

需要完成的功能:

  1. 点击 TodoAdd可以添加待办事;
  2. 左滑动 TodoItem 可以删除待办事;
  3. 勾选 待办事可以 设置待办事状态为 已完成;

3. 实现

Ant Design Mobile 渐进系列教程 4:路由嵌套 Main页面实现

1. 目标

上一章介绍 React-Router的简单实用,本文将继续介绍路由嵌套 ,React父子组件的数据传递方式。

本文中的页面组件关系如下:

其中 Main 是父页面,Index、List 与Setting页面继承自 Main,如下:

2. 原型功能

本文需要实现的功能如下:

  1. 点击Tab按钮,切换页面;
  2. 设置当前页面Tab按钮 selected;
  3. 子页面可以通过属性传入 NavBar 标题;

3. 实现

3.1 Tab 事件

我们接下来,要做的任务是:

  1. 点击Tab按钮,切换页面;

思路:Tab 点击事件触发后,将浏览器的 URL 设置为对应的锚标记。

Tab的点击事件为 onPress,如下官方例子所示:

<TabBar.Item 
...
     onPress={() => {
       this.setState({
         selectedTab: m.url,
       });
     }} 
...

此例子的 this.setState 只是为了切换 TabBar Item的 选中状态(selected),我们可以修改为:

<TabBar.Item 
...
     onPress={() => {
       this.linkTo(m.url);
     }} 
...
linkTo(link) {
     this.context.router.push(link);
  },

this.context.router 需要先声明router为context的对象:

contextTypes: {
        router: React.PropTypes.object
  },

此功能的 预览效果如下:(开始时请等待2秒)
1

总结一下:

  1. 完成目标:点击Tab按钮,切换页面;
  2. 未解问题: 点击底部 Tab后,按钮没有被选中;
  3. 引入Bug:连续点击中间的Tab会产生警告:

    Warning: You cannot PUSH the same path using hash history

意思是: 要跳转的链接跟当前链接不能相同。

不用担心,#2 和 #3 其实是一个问题,将#2解决后,Tab状态为当前链接的Tab为selected,就不会响应 onPress事件了,所以#3也就不存在了。

3.2 Tab selected

上一节,点击 【清单】按钮时,状态没有修改为 selected,我们参考官方例子知道设置为选中其实很简单,对onPress里设置 selectedTab 即可:

 linkTo(link) {
     this.context.router.push(link);
     this.setState({selectedTab: link});
  },

但是,这样也不是最完美的,加入我直接输入

http://localhost:8989/#/list

就不会执行 onPress 函数,所以解决方案应该还要初始化
selectedTab ,实现在 getInitialState 函数开始:

  getInitialState() {

    let link=this.getLink()||'index';

    return {
      selectedTab: link,
    };
  },

  getLink(){
      let links = window.location.hash.match(/(\w+)/g);
      if (!links)
          return null;
      return links[0].toLowerCase();
    },

注:其中 getLink函数里的 window.location.hash.match(/(\w+)/g);
意思是:获取最后一个 / 之后的单词。

上面代码的预览效果如下:
2

3.3 NavBar Title

上面两个例子,我们已经完成切换了 TabBar 与router 的配合使用。

我们可以发现Main页面的 标题区域还没有根据不同的子页面切换。

Main 和 List 的关系是父子组件,如下:

NavBar Title, title 定义在子组件List内, 要传递给 父组件Main,并设置到父组件的方法中。

React的通信是单向的,即从父组件到子组件,总结组件间的通讯场景如下:

  1. 父组件->子组件:props
  2. 子组件->父组件:callback
  3. 子组件->子组件:子组件通过回调改变父组件中的状态,通过props再修改另一个组件的状态

    所以,我们这个例子符合场景2,通过 callback 子组件的数据送给父组件 。

3.3.1 List组件中新建 Prop

  getDefaultProps : function () {
      return {
        title : '历史列表'
      };
    },

3.3.2 Main组件定义 callback函数

增加一个 state: title;

 getInitialState: function() {
    return {
      title: "没有传入标题"
    };
  },
  
   setTitle: function(title) {
    this.setState({title: title});
  },
  
  
  render() {
  <NavBar ref="myNavBar" mode="dark" iconName="">
            {this.state.title}
          </NavBar>
},

将 setTitle 传递给 子组件 List:

 render() {
      ....
            <ContextBox>
              {this.props.children && React.cloneElement(this.props.children, {
                  "setTitle":this.setTitle
                })}
            </ContextBox>
           
           ....
},

注:由于咱们使用了react-router,父子组件的嵌套方式是借助 this.props.children,导致我们不能直接将 属性增加到子组件的标签内,需要React.cloneElement来增加子组件的属性。详情参考 React router and this.props.children - how to pass state to this.props.children

3.3.3 子组件使用callback

   componentDidMount(){
      let title = this.props.title;
      
      //callback: setTitle
      this.props.setTitle(title);
  },

3.3.4 效果预览

Index 与 Setting 页面 参考 List 页面修改后:

3

4. 小结

本文主要实现了三个功能:

  1. 点击Tab按钮,切换页面;
  2. 设置当前页面Tab按钮 selected;
  3. 子页面可以通过属性传入 NavBar 标题;

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4

5. 更新

本文提到的功能,除了使用路由的嵌套外,直接将重复的内容封装为父控件,更更简单。

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4-1

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

Ant Design Mobile 渐进系列教程 3:路由

1. 目标

本文会介绍 ADM框架中如何使用 React-Router, 在开始本章之前,请先阅读 阮一峰的博文 React Router 使用教程

首先,我们要问一个问题:为什么要使用 Router ?

由于我们用React开发的App基本都属于SPA(单页面应用)架构,SPA应用的复杂功能,还是会抽成多个虚拟页面来降低复杂度。

  • 实体页面之间的切换,基本是透过不同的 URL来区分;
  • 但虚拟页面之间的切换,最简单的方法是关联到 URL锚标记 ,比如:

React-Router 就是通过管理 URL,实现 React 组件的切换和状态的变化,复杂的应用都会用到路由,本文会将 React-Router 引入 我们的App。

2. 原型图

实现的功能如下:

注意:Index、List 与 Setting 这三个页面都会共用 Main页面的内容。

3. 实现

3.1 环境安装

npm install --save react-router

3.2 index.web.js 在入口处 填写 Router映射

import React from 'react';
import ReactDOM from 'react-dom';

import Main       from './pages/Main';
....


import { 
  Router, 
  Route, 
  IndexRedirect, 
  hashHistory 
} from 'react-router';


let RouterMap =(
  <Router history={hashHistory}>

    <Route path="/login"    component={Login} />
    <Route path="/register" component={Register} />
  
      <Route path="/"       component={Main}>
        <IndexRedirect to="index"/>
        <Route path="/index"    component={Index}/>
        <Route path="/list"     component={List}/>
        <Route path="/setting"  component={Setting}/>
      </Route>
  </Router>
);

ReactDOM.render(RouterMap, document.getElementById('root'));

3.3 pages/Main.js 抽成共用组件

<ContextBox>
    {this.props.children}
</ContextBox>

3.4 pages/Index.js 页面

import React from 'react';

export default React.createClass({
  render() {
    return (
      <p>TODO 主页</p>
    );
  },
});

其他几个页面类似:

  • pages/List.js
  • pages/Setting.js
  • pages/Login.js
  • pages/Register.js

4 测试结果

切换 URL 的锚标记 可以完成 页面组件的切换。

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo3

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

Ant Design Mobile 渐进系列教程 2:组件

1. 目标

整个系列,我们会完成一个 TODO 的可用程序, 本文主要针对 ADM的 tab 组件。

2. 原型图

页面的组件的组成方式如下:。

注: 蓝色分支为ADM已有控件。

3. 实现

3.1 NavBar 组件

NavBar 本身在 ADM中已经存在了, 直接引入即可,如下:

import { NavBar } from 'antd-mobile';
export default React.createClass({
  render() {
    return (
      <div>
          <NavBar mode="light" iconName="">
            任务列表
          </NavBar>

          //....
      </div> 
    );
  },
});

效果如下:

3.2 ContentBox 组件

  • components/ContentBox.js 的代码如下:
import React from 'react';

import  '../style/ContextBox.less';

export default React.createClass({

  render() {

    return (
      <div className="contextBox">

      {this.props.children}

      </div>
    );
  },
});

  • 其中 style/ContentBox.less 的代码如下:

@tabHeight:120px;

.contextBox{
  width: 100%;
  height: 60%;
  margin-bottom: @tabHeight; 
  background-color:#eee;
  position: absolute;
  overflow: scroll;
}

3.3 TabListBar 组件

  • components/TabListBar 组件 源码 如下:

import React from 'react';


import { TabBar } from 'antd-mobile';
import  {Menu} from '../data/Menu';

export default React.createClass({
  getInitialState() {
    return {
      selectedTab: 'todo',
    };
  },

  renderContent() {
      return (
        <div>{this.props.children}</div>
      );
  },

  renderMenu() {
      let menus = [];
      let i =0;

      for(i=0;i<3; i++)
      {
        let m = Menu[i];
        menus.push(
        <TabBar.Item
          icon={{ uri: 'https://zos.alipayobjects.com/rmsportal/'+m.icon+'.png' }}
          selectedIcon={{ uri: 'https://zos.alipayobjects.com/rmsportal/'+m.selected+'.png' }}
          title={m.title}
          key={m.url}
          selected={this.state.selectedTab===m.url}
          onPress={() => {
            this.setState({
              selectedTab: m.url,
            });
          }}
        >
          {this.renderContent()}
        </TabBar.Item>
        );

      }
      return menus;
  },


  render() {

    let menus = this.renderMenu();

    return (
      <TabBar
        unselectedTintColor="#949494"
        tintColor="#33A3F4"
        barTintColor="white"
      >


      {menus}
        
       
      </TabBar>
    );
  },
});

  • data/Menu 数据源码 如下:
import React from 'react'
const Menu =[
    {
      title:"代办事", 
      url: "todo",
      icon:"UNQhIatjpNZHjVf",
      selected:"HLkBvJOKnmOfBPO",
    },
    
    {
      title:"清单", 
      url: "list",
      icon:"YWpPVCVOnJoCYhs",
      selected:"WadBBxOIZtDzsgP",
    },
  
    {
      title:"我的", 
      url: "mine",
      icon:"EljxLrJEShWZObW",
      selected:"LWNaMdwAFSmYBFw",
    }
  
  ];

export {Menu};

3.4 pages/Main 页面

  • pages/Main.js 的源码如下:
import React from 'react';


import { NavBar } from 'antd-mobile';

import ContextBox from '../components/ContextBox';
import TabListBar from '../components/TabListBar';

export default React.createClass({
  render() {
    return (
      <div>
          <NavBar mode="dark" iconName="">
            任务列表
          </NavBar>

          <TabListBar>

            <ContextBox>
              <p>任务内容</p>
              <p>....</p>
            </ContextBox>

          </TabListBar>

      </div> 
    );
  },
});

4. 运行结果

至此, 我们app的页面框架已经实现基本实现了,但是有个问题,点击下面的Tab时, ContextBox里的内容无法变化,下一章节我们会引入 Route 来解决这个问题。

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo2

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

Ant Design Mobile 渐进系列教程 1:入门

1. 前言

Ant Design Mobile 是阿里蚂蚁金服团队维护的高质量移动应用的前端框架,除了40多个通用组件,更有意义的提出一套类似Ant Design的设计指南和场景模式。 框架的体验确如其slogin一样:微小·确定·幸福。

Ant Design Mobile框架是基于 react native,扩展支持Web组件,不仅可以编译成native的 iOS 与 android App,而且还提供了高性能的HTML5支持(微网站、微信公众号等)。

用框架解决移动应用跨端问题:

为了描述方便,下文将 Ant Design Mobile 简称为 ADM

2. 环境

目前 Ant Design Mobile 还没发布 1.0版本,入门(类似Getting Start)的文档暂时还没有。 但是,开发团队在 问题#56 中给出了一个简单的入门实例:

git clone https://github.com/ant-design/antd-init.git
cd boilerplates/MobileDemo

也可以从 coding.net上下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4

参考 Readme 文档中的安装命令:

npm install npm@3 -g
npm install react-native-cli -g
npm install

3. 第一个例子

环境安装成功后,无需修改一行代码:

npm start

在浏览器中 localhost:8989 可以看到效果:

好了,对于Ant desgin mobile 框架,我们已经算入门了,接下来我们将继续开启【微小·确定·幸福】的Ant desgin mobile之旅…

注: 本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo1

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

React 边写边学 3 使用Webpack自动化管理

缘由

《React 边写边学 2:使用Babel》最后的章节里,很多执行命令,非常麻烦。本人将介绍Webpack自动化管理部署 React 项目。

Webpack介绍

npm install --save-dev webpack webpack-dev-server
npm install --save-dev css-loader style-loader

webpack教程参考:
* Webpack 中文指南
* React结合Webpack使用

配置文件介绍

此次项目是在 《React 边写边学 2:使用Babel》 基础上展开的。

  • webpack.config.js 文件
var path = require('path');
var webpack = require('webpack');
var CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  entry: './src/index.jsx', // 入口文件,单入口 index.jsx 文件
  output: { path: __dirname, filename: 'bundle.js' }, // 编译到的文件
  module: {
    loaders: [ // 使用特定的加载器 loader 处理特定的文件
      {
        test: /.jsx?$/, // 文件过滤规则
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react'] // es2015 处理 ES6 语法,react 处理 jsx 语法
        }
      },
      
      {test: /\.css$/, loader: 'style!css'}
    ]
  },
  plugins:[
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false
        }
      }),
      
      new CompressionPlugin({
            asset: "[path].gz[query]",
            algorithm: "gzip",
            test: /\.js$|\.html$/,
            threshold: 10240,
            minRatio: 0.8
        })
  ]
};

文件格式与结构与说明:

  • package.json 文件
{
  "name": "3_webpack",
  "version": "1.0.0",
  "description": "",
  "main": "./src/index.jsx",
  "scripts": {
    "start": "webpack-dev-server -p --hot --progress --colors"
  },
  "author": "Guixue",
  "license": "ISC",
  "devDependencies": {},
  "dependencies": {
    "babel": "^6.5.2",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "compression-webpack-plugin": "^0.3.1",
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  }
}

将 package.json文件放根目录后,安装依赖的npm库。

npm  install

执行并运行

执行webpack

npm  start

浏览器 输入 http://localhost:8080/ 即可查看效果。

总结

本文通过引入webpack 达到了 对React 项目自动化管理的目的,当然这只是简单的管理,更复杂的配置和应用可以参考 github webpack/react-starter, 到此为止,我们才完成React项目开发环境的搭建。请继续关注《React边做边学》系列其他内容。

React 边写边学 2:使用Babel

缘由

《React 边写边学 1:Hello World》用的是 CDN 上的库和转换器,正式的React项目环境我们需要使用其他的转换编译工具。

JSX 到 ES5 的编译/转换器工具有很多,比如:

  • React-tools jsx 命令
  • Jsxtransformer
  • reactify
  • Traceur
  • Babel

目前使用最多的是Babel ,Babel支持的功能也比较:

  • ES6/ES2015 转 ES5 (3个 stage)
  • JSX 转 ES5
  • ES7 转 ES5
  • CoffeeScript 转 ES5

《圣经·创世纪》第11章中说,人类最早的时候都住在一个地方,讲一种语言。他们决定造一座通天的塔,所有的人住在里面,人类再也不会分散。上帝不同意,他将人类拆散到世界各地,让人类讲不同的语言,从此难于沟通。
因此,"巴别塔"就成了混乱和语言不通的代名词,是《圣经》中最广为人知的故事之一。

Babel 的使用方法

babel src/input.jsx -o dist/output.js

将 input.jsx 格式的内容 编译为 ES5的 output.js

实例项目文件

所有项目文件

├── package.json  //npm 项目配置文件
├── .babelrc        //babel配置文件
├── index.html      //HTML 入口文件
└── src
     ├── Hello.jsx   //Hello 组件
     └── index.jsx   //入口

1 package.json

npm 项目配置文件可以手动新建

npm init            //全部回车默认
npm install --save react react-dom babel babel-cli babel-core
npm install --save   babel-preset-es2015 babel-preset-react 
npm install --save  browserify react-hot-loader webpack

新建完成后的文件如下:

{
  "name": "2_babel",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "babel src/index.jsx -o dist/index.js&&babel src/Hello.jsx -o dist/Hello.js",
    "pack": "webpack -d dist/index.js dist/all.js",
    "start": "http-server",
    "restart":"npm run build && npm run pack && npm run start"
  },
  "author": "Guixue",
  "license": "ISC",
  "devDependencies": {
    
  },
  "dependencies": {
    "react": "^15.0.2",
    "react-dom": "^15.1.0",
    "babel": "^6.5.2",
    "babel-cli": "^6.9.0",
    "babel-core": "^6.9.1",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "browserify": "^13.0.1",
    "react-hot-loader": "^1.3.0",
    "webpack": "^1.13.1"
  }
}

2 .babelrc

babel 编译配置文件,默认的编译参数

{
  "presets": ["es2015", "react"]
}

3 index.html

HTML页面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React Use Babel tool</title>
   
</head>

<body>
  

    <div id="container"></div>
    <script src="dist/all.js"></script>
</body>
</html>

4 src/Hello.jsx

Hello 组件:

import React from 'react';


export default class Hello extends React.Component{
    render() {
        return (<div>Hello {this.props.name}</div>);
    }
}

5 src/index.jsx

Index入口:

import React from 'react';
import ReactDOM from 'react-dom';


import Hello from './Hello';

ReactDOM.render(
    <Hello name="Guixue"></Hello>,
    document.getElementById("container")
);

编译并执行

  1. 安装 npm 包;
npm install
  1. 使用Babel编译 src 下的代码

或者直接执行 npm run build

babel src/index.jsx -o dist/index.js
babel src/Hello.jsx -o dist/Hello.js
  1. 使用 Webpack 打包代码

或者直接执行 npm run pack

webpack -d dist/index.js dist/all.js
  1. 启动Web Server

或者直接执行 npm run server

http-server

注: 以上内容 2-4 步骤,可以使用 npm run restart

总结

本文使用Babel编译JSX格式成功部署本地开发环境。但是,同时也带来了一个问题,步骤2的编译,如果项目中有很多文件,每个文件一条命令,肯定非常麻烦,有没有自动化的开发部署方案? 请关注系列文章《React 边写边学 3 使用Webpack自动化管理》

附:本文的参考的文档

React 边写边学 1:Hello World

背景介绍

React是Facebook出品的,目前最热门的(没有之一)前端框架,

React主页 中提到, 只三方面的内容:

  1. 只关注View层,UI的操作与展示,引入JSX自定义格式,使组件化更近简洁;
  2. 虚拟DOM,通过对DOM的模拟表现,最大限度地减少与DOM的交互。独特的diff算法,操作或渲染虚拟DOM性能非常高;
  3. 单向数据流,沿着组件树从上到下单向流动的。

第一个例子 Hello World

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React hello world</title>

  <script src="http://cdn.bootcss.com/react/15.1.0/react.min.js"></script>
  <script src="http://cdn.bootcss.com/react/15.1.0/react-dom.min.js"></script>
  <script src="http://cdn.bootcss.com/react/0.14.0-beta3/JSXTransformer.js"></script>

</head>

<body>
  
    <div id="example"></div>

    <script type="text/jsx">
      var HelloMessage = React.createClass({
        render: function() {
          return <h1>Hello {this.props.name}</h1>;
        }
      });

      ReactDOM.render(
        <HelloMessage name="World" />,  
        document.getElementById('example')
      );
    </script>
    
</body>
</html>

代码运行结果:

注: 如果要在浏览器中查看 VDOM 标签,推荐 React Devtools 插件, Chrome 和 Firefox 都支持。

代码解析


第一个例子,要最简单,所以本地不需要安装任何库,直接用 CDN的。

  • react React的库
  • react-dom React Dom库
  • JSXTransformer 将JSX语法转为 JS 格式,正常的生产环境中,不会使用此类的库,会用在部署前先用 Babel 工具转换后再部署。

总结

本文通过一个页面,成功运行React的第一个实例。
但是,使用CDN上的库以及JSXTransformer.js的做法,写的学习React的小例子还行,真正做项目肯定不行,那怎么做呢? 请阅读系列第二章 《React 边写边学 2:使用Babel》

React 组件库 Antd入门:写一个 Blog 例子

1. 需求分析

最简单的博客展示要有两个页面组成:

  1. 博客列表
  2. 博客详情页

1. 1 博客列表页面 Blog Home Page

博客首页列表

1. 2 博客详情页 Blog Detail Page

博客详情页

2. 路由与布局

2.1 安装环境

npm install antd-init -g
mkdir blog && cd blog
antd-init
npm install

3. 子组件实现

TBD…

4. 后台数据实现

TBD…

React 组件库 Antd 的使用入门

环境准备

安装 antd项目模板:

git clone https://github.com/YuhangGe/react-antd-template.git
cd react-antd-template
npm install 

测试例子:

npm run dev

在浏览器中输入 http://localhost:8000 即可访问

源码分析

代码结构:

App 组件的基本层次:

WeUI 开发示例

1. 开发环境

1.1 下载 weui 库

安装开发环境

mkdir weiixn-demo
npm init
npm install --save-dev react-weui

react 等依赖库都会自动下载到 node_modules 目录下。

另外,jsx 的解析工具需要添加,如下:

npm install --save-dev babel

1.2 webpack 使用