[技术干货] 【微码开发】微码开发入门 - React 模板开发

在打开微码react模板后会自动安装项目依赖:



安装完依赖后,资源管理器的 “WECODE TOOLS”会出现菜单,点击“本地调试”,将会开始编译运行项目,最后将在默认浏览器打开首页 ( src/routes/Home/index.js )。


到这里,项目已经跑起来了,可以开始加入我们想要的功能了。
我们来看一下index.js:

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import i18n from 'i18n';
import { Link } from 'react-router-dom';
import * as home from '../../actions/home';
import * as css from './index.module.css';
@connect(
  state => ({ ...state.home, ...state.global }),
  dispatch => bindActionCreators({ ...home }, dispatch))export default class Home extends React.Component {
  async componentWillMount() {
    window.HWH5.navTitle({ title: 'Hello World' });
    const {
      homeInfo, dataList, getHomeInfo, getFetchDemo    } = this.props;

    if (!homeInfo) {
      getHomeInfo().then((data) => {
        document.title = data.title;
      });
    }
    if (!dataList || dataList.length === 0) {
      getFetchDemo();
    }
  }
  componentDidMount() { }
  componentWillUnmount() { }
  // 打开新的窗口
  openWebview(url) {
    window.HWH5.openWebview({ uri: url });
  }
  render() {
    return (
      <div className={css.App}>
        <div className={css['App-header']} />
        <h1 className={css['App-title']}>{i18n.t('common:welcome')}</h1>
        <p className={css['App-intro']}>
          {i18n.t('home:getStart')} {i18n.t('home:edit')}
          <code>src/routes/Home/index.js</code> {i18n.t('home:saveReload')}
        </p>
        <Link to="/desc" className={css['desc-link']}>
          使用说明        
        </Link>
      </div>
    );
  }
}
Home.propTypes = {
  homeInfo: PropTypes.object,
  dataList: PropTypes.array,
  getHomeInfo: PropTypes.func,
  getFetchDemo: PropTypes.func
};

我相信如果没接触过react的看到这里应该有些懵圈,这看起来应该是js代码,但里面又穿插着类似XML的东西?来看下 render 函数中的代码:

return (
  <div className={css.App}>
    <div className={css['App-header']} />
    <h1 className={css['App-title']}>{i18n.t('common:welcome')}</h1>
    <p className={css['App-intro']}>
      {i18n.t('home:getStart')} {i18n.t('home:edit')}
      <code>src/routes/Home/index.js</code> {i18n.t('home:saveReload')}
    </p>
    <Link to="/desc" className={css['desc-link']}>
      使用说明    
    </Link>
  </div>
);

返回的这一段内容就是JSX语法了,由于篇幅关系,如果对JSX语法不了解的可以去简单学习下,很快就能上手。

接下来我们来实现一个简易版的新闻功能,它有一个列表页、详情页,在详情页会有一个分享功能,用于分享到好友或者群

首先来设计主页,也就是列表页,以 src/routes/Home/index.js 作为主页。
先加入一个导航栏,这里使用官方提供的UI组件(https://open.welink.huaweicloud.com/wecode/docs/dev/reactui_cloud/navbar/navbar.html?v=1547690386) 。
首先引入weui组件:

import { Tab, TabBody, NavBar, NavBarItem, Article } from '@wecode/react-weui';

接下来在render方法中加入组件:

render() {
  return (
    <Tab>
      <NavBar>
        <NavBarItem>
          热点        </NavBarItem>
        <NavBarItem>
          推荐        </NavBarItem>
        <NavBarItem>
          原创        </NavBarItem>
      </NavBar>
      <TabBody style={{ backgroundColor: '#fff', paddingTop: '40px' }}>
        
      </TabBody>
    </Tab>
  );
}

保存后在浏览器看到效果如下:



接下来设计一个列表,这里还是使用官方UI组件中 ListView组件,如果你需要个性化列表样式,请自行设计,打开(https://open.welink.huaweicloud.com/wecode/docs/dev/reactui_cloud/input/listview.html?v=1547690386) 引入UI组件,因为有三个分类,所以列表也需要有三个容器包裹,这样等下好做导航切换效果:

<TabBody style={{ backgroundColor: '#fff', paddingTop: '40px' }}>
  <Cells>
    <ListView
      src={news1}
      title="亚美尼亚 被神灵和奇迹环绕的国度"
      footer="07-19 09:05"
    />
  </Cells>
  <Cells>
    <ListView
      src={news2}
      title="到手的金牌飞了!英跳水名将最后一跳领先30分 却横拍入水抱女友痛哭"
      footer="07-18 21:31"
    />
  </Cells>
  <Cells>
    <ListView
      src={news3}
      title="史陶芬伯格刺杀希特勒,这个行动成功概率有多少?"
      footer="07-19 08:36"
    />
  </Cells>
</TabBody>

保存看效果:



好啦,一个新闻列表的结构大致完成了,当然这里的数据是写死的,具体开发中需要使用后端接口提供的数据,关于如何使用网络请求,请参考官方api:在微码中发送网络请求(https://open.welink.huaweicloud.com/wecode/docs/dev/jsapi/fetch/fetchinternet_cloud.html?v=1547690385)

接下来实现导航栏的切换效果,了解react的同学都会知道react是数据驱动的,也就是 state 中的数据改变,则组件中任何引用这个数据的地方都会发生改变。利用这一点,我们做导航的切换效果就不需要操作dom了,只需要适时改变state就可以了。
首先需要申明一个state,并初始化一个key为tab:

export default class Home extends React.Component {
  state = {
    tab: 0
  };
}

接下来在在每个列表中引用这个key,那么问题来了,如何通过这个key的值来控制列表的显示与隐藏呢?我们设想一下,列表是按0,1,2的顺序排列下去的,如果我们判断下如果tab的值正好等于列表自身的index值,那么就设置display为显示状态,反之则隐藏,这岂不是完美了?O(∩_∩)O~
给列表组件加入下面的条件

render() {
  const { tab } = this.state;
  <Cells style={{ display: tab === 0 ? null : 'none' }}>
  <ListView
    src={news1}
    title="亚美尼亚 被神灵和奇迹环绕的国度"
    footer="07-19 09:05"
  /></Cells><Cells style={{ display: tab === 1 ? null : 'none' }}>
  <ListView
    src={news2}
    title="到手的金牌飞了!英跳水名将最后一跳领先30分 却横拍入水抱女友痛哭"
    footer="07-18 21:31"
  /></Cells><Cells style={{ display: tab === 2 ? null : 'none' }}>
  <ListView
    src={news3}
    title="史陶芬伯格刺杀希特勒,这个行动成功概率有多少?"
    footer="07-19 08:36"
  /></Cells>
}

style={{ display: tab === 1 ? null : 'none' }} 这样是JSX中给组件添加样式的方式,还是那句话,不了解JSX语法的,先去看看哈,很好上手的。
好像缺点什么?得有地方控制这个状态 tab 呀,好说,我们给每个tab添加上状态以及点击事件:

<NavBar>
  <NavBarItem active={tab === 0} onClick={e => this.setState({ tab: 0 })}>
    热点  
  </NavBarItem>
  <NavBarItem active={tab === 1} onClick={e => this.setState({ tab: 1 })}>
    推荐  
  </NavBarItem>
  <NavBarItem active={tab === 2} onClick={e => this.setState({ tab: 2 })}>
    原创  
  </NavBarItem>
</NavBar>

active和onClick是组件内部提供的两个属性,请参照组件官方文档。

保存就可以看到导航栏可以切换了,且下面的列表也会跟随切换。




好了,接下来需要设计一个详情页。详情页就是一篇文章,实际开发中,整个文章dom结构都是从接口得到的,这里使用静态内容做一个示范。
将src下的Desc目录更名为News,作为详情页,且需要在app.json中同步更改:
src/app.json:

{
  "pages": {
    "./Home/index": "/",
    "./News/index": "/news"
  }
}

src/routes/News/index.js

import React from 'react';
import './index.css';
import news1 from '../../../public/img/news1.jpg';

export default function News() {
  return (
    <div className="article-content">
      <div className="head">
        <h1 className="title">亚美尼亚 被神灵和奇迹环绕的国度</h1>
        <div className="info">
          <span className="time js-time">2019-07-19 09:05</span>
        </div>
      </div>
      <div className="content">
        <p>当飞机在机场上空盘旋,等待降落到跑道上时,你可趁机望望窗外的风景。在你面前,有如明信片上的照片一样美丽的就是亚拉拉特山。它是世界上最著名的山之一,毋庸置疑也是宗教地位最高的山之一。虽然三大亚伯拉罕宗教(犹太教、基督教与伊斯兰教)的教徒在许多事情上存在分歧,但他们都相信《旧约·创世纪》中的描述,即当上帝让世界发洪水的时候,诺亚方舟最后停泊的地方就在亚拉拉特山。</p>
          <div className="photo"><img alt="" src={news1} /></div>
          <p>虽然有许多人不相信诺亚方舟的故事,但在高加索的这一部分地区,确有一些地理证据表明这里曾发过大洪水。如果真的是这样,虽然洪水无疑为当地的民众带来了灾难(并在此之后,被他们的民间传说收录进来,口耳相传),但洪水影响的也可能仅仅是一小部分地区。一些地理学家与考古学家曾暗示洪水可能是海啸造成的,而在古代人看来,那就是上帝在发怒。亚拉拉特山现在已经不属于亚美尼亚了,它是国境线另一侧的土耳其的领土,对这一点亚美尼亚人格外敏感,但是这座山依旧是亚美尼亚人的民族认同的一个重要的部分。亚美尼亚民族自称是诺亚最小的儿子雅弗的后代;亚拉拉特山出现在国徽与银行发行的纸币上;在亚美尼亚的古代传说中,亚拉拉特山是众神之家,这点非常像古希腊的神都居住在奥林匹斯山一样。亚拉拉特山主宰着埃里温的天际线,亚美尼亚人称,这是一个永恒的提示,即人类在地球上生活是因了上帝的恩泽。</p>
      </div>
      <div id="shareBtn" className="share-btn">
        +
      </div>
    </div>
  );
}

看下效果:



News/index.css中加入一些样式让页面看起来协调一些:

.article-content{ padding: 10px; }
.article-content .title{ font-size: 24px;}
.article-content .info{ font-size: 14px; color: #999; padding: 10px 0;}
.article-content p{ text-indent: 2rem; }
.article-content img{ width: 100%; margin: 10px 0; }
.share-btn{ display: inline-block; background: #ff6f6f; width: 2rem; height: 2rem; text-align: center; color: #fff; font-size: 2rem; line-height: 2rem; padding: 5px; border-radius: 50%; position: fixed; right: 10px; bottom: 50px; }


同时,在 src/routes/News/index.js 中给列表加上链接,指向详情页:

<Link to="/news">
  <ListView
    src={news1}
    title="亚美尼亚 被神灵和奇迹环绕的国度"
    footer="07-19 09:05"
  />
</Link>

注意到这个红色的分享按钮了吗?接下来将调用JSAPI做一个分享功能,最终分享到群或者聊天界面显示的是一张卡片。使用的是 HWH5.share 这个方法,来看一下参数调用:

参数 类型 是否必填 说明
type String 类型
title String 标题
h5Uri String h5页面的Uri
from String 分享来源,可以填微码的应用名称
desc String 描述,title下面会紧接一段描述文本
pcUri String PC端点击打开的链接。isPCDisplay为1时,该参数为必传参数
isPCDisplay Number 是否在PC显示分享信息。1:显示,0:不显示
iconURL String 分享图标

这里面最重要的一个参数 h5Uri 就是微码应用在CloudLink中url链接地址,在根目录下的 plugin.json 中可以看到:

{
  "indexURL": "h5://20190709172642809/html/index.html",
  "appId": "20190709172642809",
  "minSdkVersionName": "1.2.8",
  "versionName": "1.0.0",
  "permissions": [
    "outerNet",
    "contact",
    "file",
    "media"
  ]
}

其中 indexURL 就是我们要用到的参数 h5Uri,好了,开始写分享这一部分的代码,首先定义一个分享方法:

export default class News extends React.Component {handleShareBEvent() {
  const _shareParams = {
    title: '亚美尼亚 被神灵和奇迹环绕的国度',
    desc: '亚美尼亚 被神灵和奇迹环绕的国度',
    h5Uri: 'h5://20190709172642809/html/index.html',
    iconURL: 'https://open.welink.huaweicloud.com/wecode/image/icon/20190709/7ab9c09c-81c5-45d9-97cf-9b52cd01e676.png',
    from: 'simpleDemo'
  };

  window.HWH5.share({
    type: 'IM',
    data: _shareParams  }).catch(error => {
    console.log('分享发生异常', error);
  })
}

然后给分享按钮绑定这个方法:

<div id="shareBtn" className="share-btn" onClick={this.handleShareBEvent.bind(this)}>
  +
</div>

title 填上创建应用时填写的应用名称,针对于当前页面,我填上了新闻的标题,具体情况具体对待
desc 写上简单的描述,针对于当前页面,我填上了新闻的简介
h5Uri 填上plugin.json中的 indexURL 字段值
iconURL 分享卡片中的缩略图,在应用详情中可以找到,也可以使用当前分享页面的具体图片,更为贴切
from 填上应用英文名
父级参数 type 就填写 ‘IM’。

到这里一个简单的新闻带分享功能就完成了,react 模板的关键是要掌握react的一些核心,例如 state、props、生命周期函数,以及JSX语法等等,了解了这些以后对于微码开发也就信手拈来了。