美文网首页程序员React Native学习React.js学习
从0到1构建适配不同端(微信小程序、H5、React-Nativ

从0到1构建适配不同端(微信小程序、H5、React-Nativ

作者: EasyTuan | 来源:发表于2018-10-01 09:22 被阅读18次

    从0到1构建适配不同端(微信小程序、H5、React-Native 等)的taro + dva应用

    写在前面

    Taro 是一套遵循 React 语法规范的 多端开发 解决方案。现如今市面上端的形态多种多样,Web、React-Native、微信小程序等各种端大行其道,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。

    使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信小程序、H5、React-Native 等)运行的代码。

    技术栈

    React + taro + dva

    taro的安装及使用

    安装 Taro 开发工具 @tarojs/cli

    使用 npm 或者 yarn 全局安装,或者直接使用npx

    $ npm install -g @tarojs/cli
    $ yarn global add @tarojs/cli
    

    使用命令创建模板项目

    $ taro init myApp
    

    进入项目目录开始开发,可以选择小程序预览模式,或者 h5 预览模式,若使用微信小程序预览模式,则需要自行下载并打开微信开发者工具,选择预览项目根目录。

    微信小程序编译预览模式

    # npm script
    $ npm run dev:weapp
    # 仅限全局安装
    $ taro build --type weapp --watch
    # npx 用户也可以使用
    $ npx taro build --type weapp --watch
    

    H5 编译预览模式

    # npm script
    $ npm run dev:h5
    # 仅限全局安装
    $ taro build --type h5 --watch
    # npx 用户也可以使用
    $ npx taro build --type h5 --watch
    

    RN 编译预览模式

    # npm script
    $ npm run dev:rn
    # 仅限全局安装
    $ taro build --type rn --watch
    # npx 用户也可以使用
    $ npx taro build --type rn --watch
    

    当然到这一步有个大概的骨架,作为生产开发是不够的,这时候我们引入dva

    $ npm i dva-core dva-loading --save
    

    新建dva.js

    import { create } from 'dva-core';
    import { createLogger } from 'redux-logger';
    import createLoading from 'dva-loading';
    
    let app;
    let store;
    let dispatch;
    
    function createApp(opt) {
      // redux日志
      // opt.onAction = [createLogger()];
      app = create(opt);
      app.use(createLoading({}));
    
      if (!global.registered) opt.models.forEach(model => app.model(model));
      global.registered = true;
      app.start();
    
      store = app._store;
      app.getStore = () => store;
    
      dispatch = store.dispatch;
    
      app.dispatch = dispatch;
      return app;
    }
    
    export default {
      createApp,
      getDispatch() {
        return app.dispatch;
      }
    }
    

    并在入口文件导入

    import dva from './utils/dva'
    const dvaApp = dva.createApp({
      initialState: {},
      models: models,
    });
    const store = dvaApp.getStore();
    

    dva集成好了,下面我们来封装下request网络请求吧

    import Taro from '@tarojs/taro';
    import { baseUrl, noConsole } from '../config';
    
    export default (options = { method: 'GET', data: {} }) => {
      if (!noConsole) {
        console.log(`${new Date().toLocaleString()}【 M=${options.url} 】P=${JSON.stringify(options.data)}`);
      }
      return Taro.request({
        url: baseUrl + options.url,
        data: options.data,
        headers: {
          'Content-Type': 'application/json',
        },
        method: options.method.toUpperCase(),
      }).then((res) => {
        const { statusCode, data } = res;
        if (statusCode >= 200 && statusCode < 300) {
          if (!noConsole) {
            console.log(`${new Date().toLocaleString()}【 M=${options.url} 】【接口响应:】`,res.data);
          }
          if (data.status !== 'ok') {
            Taro.showToast({
              title: `${res.data.error.message}~` || res.data.error.code,
              icon: 'none',
              mask: true,
            });
          }
          return data;
        } else {
          throw new Error(`网络请求错误,状态码${statusCode}`);
        }
      })
    }
    

    好了,是应该准备pages页面的开发了,本人比较喜欢umi的目录结构

      pages                  // 页面文件目录
        └── home
            ├── index.js           // 页面逻辑
            ├── index.scss         // 页面样式
            ├── model.js           // 页面models
            └── service.css        // 页面api
    

    一个page要生成4个文件?能否用脚本帮我们自动生成呢?那来写一个吧

     /**
     * pages模版快速生成脚本,执行命令 npm run tep `文件名`
     */
    
    const fs = require('fs');
    
    const dirName = process.argv[2];
    
    if (!dirName) {
      console.log('文件夹名称不能为空!');
      console.log('示例:npm run tep test');
      process.exit(0);
    }
    
    // 页面模版
    const indexTep = `import Taro, { Component } from '@tarojs/taro';
    import { View } from '@tarojs/components';
    import { connect } from '@tarojs/redux';
    import './index.scss';
    
    @connect(({${dirName}}) => ({
      ...${dirName},
    }))
    export default class ${titleCase(dirName)} extends Component {
      config = {
        navigationBarTitleText: '${dirName}',
      };
    
      componentDidMount = () => {
    
      };
    
      render() {
        return (
          <View className="${dirName}-page">
            ${dirName}
          </View>
        )
      }
    }
    `;
    
    // scss文件模版
    const scssTep = `@import "../../styles/mixin";
    
    .${dirName}-page {
      @include wh(100%, 100%);
    }
    `;
    
    // model文件模版
    const modelTep = `import * as ${dirName}Api from './service';
    
    export default {
      namespace: '${dirName}',
      state: {
    
      },
    
      effects: {
        * effectsDemo(_, { call, put }) {
          const { status, data } = yield call(${dirName}Api.demo, {});
          if (status === 'ok') {
            yield put({ type: 'save',
              payload: {
                topData: data,
              } });
          }
        },
      },
    
      reducers: {
        save(state, { payload }) {
          return { ...state, ...payload };
        },
      },
    
    };
    `;
    
    
    // service页面模版
    const serviceTep = `import Request from '../../utils/request';
    
    export const demo = (data) => {
      return Request({
        url: '路径',
        method: 'POST',
        data,
      });
    };
    `;
    
    
    
    fs.mkdirSync(`./src/pages/${dirName}`); // mkdir $1
    process.chdir(`./src/pages/${dirName}`); // cd $1
    
    fs.writeFileSync('index.js', indexTep);
    fs.writeFileSync('index.scss', scssTep);
    fs.writeFileSync('model.js', modelTep);
    fs.writeFileSync('service.js', serviceTep);
    
    console.log(`模版${dirName}已创建,请手动增加models`);
    
    function titleCase(str) {
      const array = str.toLowerCase().split(' ');
      for (let i = 0; i < array.length; i++) {
        array[i] = array[i][0].toUpperCase() + array[i].substring(1, array[i].length);
      }
      const string = array.join(' ');
      return string;
    }
    
    process.exit(0);
    

    现在是时候进行愉快的开发了。。。

    目录结构

    ├── .temp                  // H5编译结果目录
    ├── .rn_temp               // RN编译结果目录
    ├── dist                   // 小程序编译结果目录
    ├── config                 // Taro配置目录
    │   ├── dev.js                 // 开发时配置
    │   ├── index.js               // 默认配置
    │   └── prod.js                // 打包时配置
    ├── screenshots            // 项目截图,和项目开发无关
    ├── src                    // 源码目录
    │   ├── components             // 组件
    │   ├── config                 // 项目开发配置
    │   ├── images                 // 图片文件
    │   ├── models                 // redux models
    │   ├── pages                  // 页面文件目录
    │   │   └── home
    │   │       ├── index.js           // 页面逻辑
    │   │       ├── index.scss         // 页面样式
    │   │       ├── model.js           // 页面models
    │   │       └── service.css        // 页面api
    │   ├── styles             // 样式文件
    │   ├── utils              // 常用工具类
    │   ├── app.js             // 入口文件
    │   └── index.html
    ├── package.json
    └── template.js            // pages模版快速生成脚本,执行命令 npm run tep `文件名`
    

    写在最后

    项目完整代码地址

    git分支说明:

    init:框架整体结构,不涉及任何业务逻辑

    master:项目的稳定版本

    feature:项目开发分支

    后续我会加入一些业务代码和小程序、H5的线上二维码,方面大家学习和交流

    相关文章

      网友评论

        本文标题:从0到1构建适配不同端(微信小程序、H5、React-Nativ

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