剑客
关注科技互联网

Git 项目推荐 | 实现小程序 CNode 社区

直播地址 live.bilibili.com/1330246

9/30 周五 8 点开播

带你实现小程序 CNode 社区

讲师介绍

主驾驶老司机:桑世龙

简介:江湖传说的狼叔,是空弦科技 CTO。开源项目 Moajs 作者,Node.js 技术布道者。 正在写一个本新书《更了不起的 Node 4:将下一代 Web 框架 Koa 进行到底》

副驾驶老司机:叶倍宏

简介:前自由职业者,远程办公,边旅行边工作,在云南大理住了 2 年。现在来广州发展思客教学,做有情怀的 IT 教学。

微信小程序是什么?

先回顾一下微信应用号发布事件

传闻已久的微信应用号终于得到证实。21日晚间,微信公众号“小道消息”发布文章称收到微信官方的微信应用号内测邀请。根据内测邀请函显示,应用号是微信公众平台提供了一种新的开放能力,开发者可以快速开发一个小程序。

有腾讯内部员工在职场社交软件脉脉上爆料:应用号以“微信公众平台小程序”的名义进行内测发布,核心功能是提供一些本地的API供H5上面的js调用,以此提升微信上H5应用的流畅度。

据爆料,根据腾讯内部数据筛选,第一批应用号内测只邀请了200个微信公众号,若还想收到邀请的公众号只能等待下一批。

早在今年年初,“微信之父”张小龙就透露出正在打造应用号的消息,而后张小龙便在2016年微信公开课PRO上,第一次正式对外公布应用号。

什么是应用号?张小龙介绍说,当用户关注了一个「应用号」之后,就相当于安装了一款 app。在「应用号」内,用户就可以实现对 app 的一些基本诉求。例如,目前许多用户会选在微信钱包中可以买机票、火车票,而不是去下载一个并不常用的买票软件;未来在「应用号」中,可以实现更多的功能。并和其他 app 一样,这个公众号平时是不会向用户主动发送内容的,因此会避免打扰。

据张小龙介绍,推出应用号基于两方面的原因。一是用户手机上可以少下载安装一些软件,平时打开频率不高的软件,可以用应用号代替;二是用户换手机时无需重复安装软件。而对于开发者,尤其是创业者来说,在应用号中实现一个功能远比开发一款 app 省钱省力许多。

一句话简介

微信小程序是限于微信提供的MINA框架提供的app开发便捷展示解决方案

实例:用cnode社区api做微信小应用

https://github.com/coolfishstudio/wechat-webapp-cnode

Why?

一次只做一件事儿,已有api,你只需要关注小程序

切莫贪多,

导入项目

添加项目

Git 项目推荐 | 实现小程序 CNode 社区

选择源码所在目录

Git 项目推荐 | 实现小程序 CNode 社区

完整信息

Git 项目推荐 | 实现小程序 CNode 社区

首页

效果

布局

Git 项目推荐 | 实现小程序 CNode 社区

example/pages/topics/topics.wxml

<!--posts.wxml-->
<view class="topics-main">
  <view class="top-bar">
    <view class="top-bar-item" id="all" catchtap="onTapTag">全部</view>
    <view class="top-bar-item" id="good" catchtap="onTapTag">精华</view>
    <view class="top-bar-item" id="share" catchtap="onTapTag">分享</view>
    <view class="top-bar-item" id="ask" catchtap="onTapTag">问答</view>
    <view class="top-bar-item" id="job" catchtap="onTapTag">招聘</view>
  </view>
  <scroll-view class="posts-list" style="height:100%" scroll-y="true" bindscrolltolower="lower">
    <block wx:for="{{postsList}}">
      <view class="posts-item" index="{{index}}" id="{{item.id}}" catchtap="redictDetail">
        <view class="author">
          <image class="author-avatar" src="{{item.author.avatar_url}}"></image>
          <view class="author-name">{{item.author.loginname}}</view>
          <view class="posts-tag hot" wx:if="{{item.top === true}}">置顶</view>
          <view class="posts-tag" wx:if="{{item.good === true}}">精华</view>
          <view class="posts-last-reply">{{item.last_reply_at}}</view>
        </view>
        <view class="posts-title">{{item.title}}</view>
        <view class="bar-info">
          <view class="bar-info-item">
            <image class="bar-info-item-icon" src="/images/icon/reply.png"></image>
            <view class="bar-info-item-number">{{item.reply_count}}</view>
          </view>
          <view class="bar-info-item">
            <image class="bar-info-item-icon" src="/images/icon/visit.png"></image>
            <view class="bar-info-item-number">{{item.visit_count}}</view>
          </view>
        </view>
      </view>
    </block>
  </scroll-view>

  <loading hidden="{{hidden}}">
    加载中...
  </loading>
</view>

三大部分

  • top-bar 分类是普通的view
  • posts-list 帖子列表,是scroll-view
  • loading 内置的组件,默认隐藏

简单点说,就是你们误会了的组件

是不是跟vue、react很像?

http://wxopen.notedown.cn/component/

Git 项目推荐 | 实现小程序 CNode 社区

帖子列表

<scroll-view class="posts-list" style="height:100%" scroll-y="true" bindscrolltolower="lower">
    <block wx:for="{{postsList}}">
      <view class="posts-item" index="{{index}}" id="{{item.id}}" catchtap="redictDetail">
        <view class="author">
          <image class="author-avatar" src="{{item.author.avatar_url}}"></image>
          <view class="author-name">{{item.author.loginname}}</view>
          <view class="posts-tag hot" wx:if="{{item.top === true}}">置顶</view>
          <view class="posts-tag" wx:if="{{item.good === true}}">精华</view>
          <view class="posts-last-reply">{{item.last_reply_at}}</view>
        </view>
        <view class="posts-title">{{item.title}}</view>
        <view class="bar-info">
          <view class="bar-info-item">
            <image class="bar-info-item-icon" src="/images/icon/reply.png"></image>
            <view class="bar-info-item-number">{{item.reply_count}}</view>
          </view>
          <view class="bar-info-item">
            <image class="bar-info-item-icon" src="/images/icon/visit.png"></image>
            <view class="bar-info-item-number">{{item.visit_count}}</view>
          </view>
        </view>
      </view>
    </block>
  </scroll-view>

布局是scroll-view,然后嵌入了

<block wx:for="{{postsList}}">

</block>

像不像ejs里的

<ul>
<% for(var i=0; i<supplies.length; i++) {%>
   <li><%= supplies[i] %></li>
<% } %>
</ul>

然后我们看看里面的单条展示

Git 项目推荐 | 实现小程序 CNode 社区

<view class="posts-item" index="{{index}}" id="{{item.id}}" catchtap="redictDetail">
        <view class="author">
          <image class="author-avatar" src="{{item.author.avatar_url}}"></image>
          <view class="author-name">{{item.author.loginname}}</view>
          <view class="posts-tag hot" wx:if="{{item.top === true}}">置顶</view>
          <view class="posts-tag" wx:if="{{item.good === true}}">精华</view>
          <view class="posts-last-reply">{{item.last_reply_at}}</view>
        </view>
        <view class="posts-title">{{item.title}}</view>
        <view class="bar-info">
          <view class="bar-info-item">
            <image class="bar-info-item-icon" src="/images/icon/reply.png"></image>
            <view class="bar-info-item-number">{{item.reply_count}}</view>
          </view>
          <view class="bar-info-item">
            <image class="bar-info-item-icon" src="/images/icon/visit.png"></image>
            <view class="bar-info-item-number">{{item.visit_count}}</view>
          </view>
        </view>
      </view>

可以看出是一个cell里分了3行

  • 第1行 author 作者、时间
  • 第2行 posts-title 标题
  • 第3行 bar-info 评论,查看次数

然后单行,以author为例子

<view class="author">
          <image class="author-avatar" src="{{item.author.avatar_url}}"></image>
          <view class="author-name">{{item.author.loginname}}</view>
          <view class="posts-tag hot" wx:if="{{item.top === true}}">置顶</view>
          <view class="posts-tag" wx:if="{{item.good === true}}">精华</view>
          <view class="posts-last-reply">{{item.last_reply_at}}</view>
        </view>

各位看到这里有啥感觉呢?

如何获取数据?

上面模板里的 for + 描述用的block,有一个postsList,如果有它就可以显示了。那么如何它在哪里呢?

其实在example/pages/topics/topics.js里的

Page({
  data: {
    title: '话题列表',
    postsList: [],
    hidden: false,
    page: 1,
    tab: 'all'
  },
  ...
})

注意data里的postsList。也就是说data里的面内容会和模板一起编译,有木有明白点什么?

和模板引擎像么?

模板引擎原理

编译(模板 + 数据)= html

你只要setData,它就会自动渲染,是不是有点像mvvm?$scope?

self.setData({
          postsList: self.data.postsList.concat(res.data.data.map(function (item) {
            item.last_reply_at = util.getDateDiff(new Date(item.last_reply_at));
            return item;
          }))
        });

只不过所有的数据都放到data作为上下文,这其实是简化了的方案。

http请求

api是获取主页列表

Git 项目推荐 | 实现小程序 CNode 社区

具体的调用wx.request向服务器发送

wx.request({
      url: Api.getTopics(data),
      success: function (res) {
        self.setData({
          postsList: self.data.postsList.concat(res.data.data.map(function (item) {
            item.last_reply_at = util.getDateDiff(new Date(item.last_reply_at));
            return item;
          }))
        });
        setTimeout(function () {
          self.setData({
            hidden: true
          });
        }, 300);
      }
    });

这里很简单

  • get请求
  • url = Api.getTopics(data)
  • success 是当请求成功的时候的回调处理

和ajax基本一样,对比$.ajax,想想

Git 项目推荐 | 实现小程序 CNode 社区

更多示例

wx.request({
  url: 'test.php',
  data: {
     x: '' ,
     y: ''
  },
  header:{
      "Content-Type":"application/json"
  },
  success: function(res) {
     var data = res.data;
  }
});

剩下的我们还需要啥呢?

后台接口api开发就够了

Page的生命周期

http://wxopen.notedown.cn/framework/app-service/page.html

Git 项目推荐 | 实现小程序 CNode 社区

群里有iOS工程师

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

页面刚进来的时候掉的viewDidLoad,而html里的onload,是不是非常类似?会有人想到dp里的模板模式么?

//index.js
Page({
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // Do some initialize when page load.
  },
  onReady: function() {
    // Do something when page ready.
  },
  onShow: function() {
    // Do something when page show.
  },
  onHide: function() {
    // Do something when page hide.
  },
  onUnload: function() {
    // Do something when page close.
  },
  // Event handler.
  viewTap: function() {
    this.setData({
      text: 'Set some data for updating view.'
    })
  }
})

tab

模板

<view class="top-bar">
    <view class="top-bar-item" id="all" catchtap="onTapTag">全部</view>
    <view class="top-bar-item" id="good" catchtap="onTapTag">精华</view>
    <view class="top-bar-item" id="share" catchtap="onTapTag">分享</view>
    <view class="top-bar-item" id="ask" catchtap="onTapTag">问答</view>
    <view class="top-bar-item" id="job" catchtap="onTapTag">招聘</view>
  </view>

事件触发

catchtap="onTapTag"

这个也是Page上下文里的方法

onTapTag: function (e) {
    var self = this;
    var tab = e.currentTarget.id;
    self.setData({
      tab: tab
    });
    if (tab !== 'all') {
      this.fetchData({tab: tab});
    } else {
      this.fetchData();
    }
  },

那么如何知道e里都有啥呢?

简单的打印log即可

Git 项目推荐 | 实现小程序 CNode 社区

简单的调试

Git 项目推荐 | 实现小程序 CNode 社区

上拉加载下一页

所有的组件能和object是一样,就2各种:属性(基本类型,Boolean,Number,String)和行为(EventHandle)

<scroll-view class="posts-list" style="height:100%" scroll-y="true" bindscrolltolower="lower">

这里的class,style,scroll-y,bindscrolltolower都是属性,唯一不一样的是bindscrolltolower,它实际上是行为

bindscrolltolower   EventHandle     滚动到底部/右边,会触发scrolltolower事件

即我们在移动端常说的上拉加载更多。

这和我们写react、vue实际上是非常类似的。

https://github.com/airyland/vux/blob/master/src/components/panel/index.vue

<template>
  <div class="weui_panel weui_panel_access">
    <div class="weui_panel_hd" v-if="header" @click="onClickHeader" v-html="header"></div>
    <div class="weui_panel_bd">
      <!--type==='1'-->
      <a :href="getUrl(item.url)" v-for="item in list" @click.prevent="onItemClick(item)" class="weui_media_box weui_media_appmsg" v-if="type === '1'">
        <div class="weui_media_hd" v-if="item.src">
          <img class="weui_media_appmsg_thumb" :src="item.src" alt="">
        </div>
        <div class="weui_media_bd">
          <h4 class="weui_media_title">{{item.title}}</h4>
          <p class="weui_media_desc">{{item.desc}}</p>
        </div>
      </a>
      <!--type==='2'-->
      <div class="weui_media_box weui_media_text" v-for="item in list" @click.prevent="onItemClick(item)" v-if="type === '2'">
          <h4 class="weui_media_title">{{item.title}}</h4>
          <p class="weui_media_desc">{{item.desc}}</p>
      </div>
      <!--type==='3'-->
      <div class="weui_media_box weui_media_small_appmsg">
          <div class="weui_cells weui_cells_access">
            <a class="weui_cell" :href="getUrl(item.url)" v-for="item in list" @click.prevent="onItemClick(item)" v-if="type === '3'">
              <div class="weui_cell_hd">
                <img :src="item.src" alt="" style="width:20px;margin-right:5px;display:block">
              </div>
              <div class="weui_cell_bd weui_cell_primary">
                <p>{{item.title}}</p>
              </div>
              <span class="weui_cell_ft"></span>
            </a>
          </div>
      </div>
    </div>
    <a class="weui_panel_ft" :href="getUrl(footer.url)" v-if="footer && type !== '3'" @click.prevent="onClickFooter" v-html="footer.title"></a>
  </div>
</template>

<script>
import { go, getUrl } from '../../libs/router'
export default {
  props: {
    header: String,
    footer: Object,
    list: Array,
    type: {
      type: String,
      default: '1'
    }
  },
  methods: {
    getUrl (url) {
      return getUrl(url, this.$router)
    },
    onClickFooter () {
      this.$emit('on-click-footer')
      go(this.footer.url, this.$router)
    },
    onClickHeader () {
      this.$emit('on-click-header')
    },
    onItemClick (item) {
      this.$emit('on-click-item', item)
      go(item.url, this.$router)
    }
  }
}
</script>

<style lang="less">
@import '../../styles/weui/widget/weui_panel/weui_panel';
@import '../../styles/weui/widget/weui_media_box/weui_media_box';
</style>

loading

其实非常简单的,定义模板

<loading hidden="{{hidden}}">
    加载中...
  </loading>

那这个 hidden="{{hidden}}" 呢?模板都是data里的,so

Page({
  data: {
    title: '话题列表',
    postsList: [],
    hidden: false,
    page: 1,
    tab: 'all'
  },

默认是false,因为一进来就要loading。。。

进来的时候通过lifecyle的onload

onLoad: function () {
    console.log(1)
    this.fetchData();
  },

然后隐藏loading的操作就在http请求数据之后了。

wx.request({
      url: Api.getTopics(data),
      success: function (res) {
        self.setData({
          postsList: self.data.postsList.concat(res.data.data.map(function (item) {
            item.last_reply_at = util.getDateDiff(new Date(item.last_reply_at));
            return item;
          }))
        });
        setTimeout(function () {
          self.setData({
            hidden: true
          });
        }, 300);
      }
    });

其中的

self.setData({
            hidden: true
          });

还得上文中说的data是全局上下午吗?

页面间跳转

模板定义

<view class="posts-item" index="{{index}}" id="{{item.id}}" catchtap="redictDetail">

注意

catchtap="redictDetail"

page里的redictDetail方法

redictDetail: function (e) {
    console.log('我要看详情');
    var id = e.currentTarget.id,
        url = '../detail/detail?id=' + id;
    wx.navigateTo({
      url: url
    })
  },

实际上有2种,上面是通过api实现的,还有一种是通过指令来实现的

<navigator url="navigate?title=navigate" hover-class="navigator-hover">跳转到新页面</navigator>
<navigator url="redirect?title=redirect" redirect hover-class="other-navigator-hover">在当前页打开</navigator>

utils定义

page里

// posts.js
var Api = require('../../utils/api.js');
var util = require('../../utils/util.js');

api.js具体内容

'use strict';
var HOST_URI = 'https://cnodejs.org/api/v1';

var GET_TOPICS = '/topics';
var GET_TOPIC_BY_ID = '/topic/';

function obj2uri (obj) {
    return Object.keys(obj).map(function (k) {
        return encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]);
    }).join('&');
}

module.exports = {
    // 获取列表数据
    getTopics: function (obj) {
        return HOST_URI + GET_TOPICS + '?' + obj2uri(obj);
    },
    // 获取内容页数据
    getTopicByID: function (id, obj) {
        return HOST_URI + GET_TOPIC_BY_ID + id + '?' + obj2uri(obj);
    }
};

感觉跟啥像?

Promise?

Git 项目推荐 | 实现小程序 CNode 社区

wxss

http://wxopen.notedown.cn/framework/view/wxss.html

缓存

这里没有使用,如果使用登录的时候就需要了,比如token

文档 http://wxopen.notedown.cn/api/data.html

k-v的,还是比较有限的

websocket

  • pub/sub
  • im

canvas

高级玩法

https://github.com/MeCKodo/wxapp-cli

优势

1.可以在任意IDE中开发

2.可使用ES6或ES5

3.支持sass和less

4.可以同时编写.html|.wxml,.wxss|.scss|.less 文件,最后都会转换为.wxml和.wxss

5.编写完任何文件(包括.json)只需要去微信开发者工具中点击重启即可预览

6.NODE_ENV 环境切换 (dev|production)

7.支持eslint (在gulpfile文件打开36行注释即可,下个版本会集成到cli配置选项中)

劣势

1.由于微信封闭的环境内,所以没有sourcemap,但这不太影响调试(即使是经过编译后的代码,本人测试了出bug的代码,还是可以从控制台跳到源码的地方)

2.由于微信封闭的环境内,无法实现reload或者hot reload

PS: 当然如果你不想写ES6也是完全可以的 在后面统一介绍命令

Q & A总结

微信小程序开发需要具体哪些技术知识?

  • 会js、css
  • 理解移动端h5相关开发概念
  • 使用微信提供的wxml和wxss
  • 熟悉微信提供api
  • 熟悉http协议

适合0基础的前端工程师学习?

和h5类似,入门简单,精通很难

微信小程序跟HTML5、Web APP的关系

  • 小程序不是HTML5,也不是Web APP
  • 都是展示层的
  • 基础知识都是一样的,移动端,http协议等
  • api和开发方式有一定差异,都属于受限开发
  • 小程序的抽象程度更高,与是h5实现还是native实现无关
  • 和Web App关系不大,但会Web App学这个还是比较容易的

怎么学习微信小程序,如果没有邀请码,可以怎么学?

上面讲过了

如何看待h5?

我曾讲过,未来是h5的,原因有2

硬件越来越牛逼,廉价,内存不会成为限制条件

网络带宽越来越牛逼

这估计也是目前h5受限的2个主要点吧。

微信公众号时代,引入h5,以及手游,算是让h5火了一把,但整体来说开发体验并不好,一般大家只用h5写一些交互少、偏于展示层的东西。而完整的hybrid应用,还是要有一定比例的借助native来实现一些原生功能的。

无论是h5虚拟化也好,还是各种折腾,比如cordova,react natvie也好,我一直认为它们是过渡状态,从native到h5的过程中,限于条件妥协的产物。当然,在目前来看,想要取得好的效果,难免要使用它。

最近几年,被h5冲击的native开发越来越惨淡(相比2010年到2014年),甚至有人说app已死,开发公众号h5就可以了。这是客观的某种事实,确实微信有用户基数,是比较好的入口,另外你需要的功能基本上都可以实现。而且成本上,相对要低一些。

可,它真的很完美么?

不见得吧,1)跨平台是永远的痛,一般连iOS6+,Android 4+都很难兼顾,有时候讨厌的让人不禁想起f**k ie6.。。 2)各个浏览器,版本实现不一致,以存储为例,localstorage,sqlite,indexedb等,每个版本可能都不一定支持,又何谈通用性呢?混乱不是不能解决,成本问题,3)开发看着容易,但你很难找到好的开发人员。写的话大家差不多都可以,但谈到优化,大部分人都怂了,这其实也是目前h5效果不好的原因。

在平衡时间和实现之间,有时我们忍了。。。这大概就是未来吧

说了一堆h5的缺点,也说说它的优点吧,目前无数开拓者,前仆后继,都投入到这个坑里,无数的解决方案,框架,优化,每天都在产生。我们现在觉得前端发展无比迅速,其实很大比例是h5推动的,它已经是前端领域必不可少的组成部分。无论是vue 2, ng2,ionic2等都是比较优秀的,甚至基于weui都衍生出大把的框架。这是时代带给我们的,是挑战,也是机遇

再说说人的问题

传统的前端不会h5都不好意思说自己是前端,目前招聘最火就是前端了,面试是不可能不问h5相关问题的

iOS和Android开发,如果不会h5,未来的出路会越来越窄,当下北京的iOS培训出来的都很难找工作(甚至有不要薪水蹭经验的)。对于那些在职的人来说,每天领导都在“算计”他们,要不hybrid?

h5就像前端开发里躲不开Node.js一样,无论你是做什么的,你都绕不开h5

再回头说微信应用号这事儿,微信会变成OS(操作系统),以后大家只要用我就好了,把其他应用变成微信下面的子应用,这下世界就清净了。以前一直觉得chrome os的理念很先进,不想竟然微信实现了。。。

微信下一步可能要和手机厂商合作了,手机里只有微信OS,打开就是微信界面,然后想安装应用,请打开微信OS里的h5 app store。。。

是不是挺吓人的一件事儿?估计其他大厂又要躲在厕所哭泣:不要让我的app下架。。。,微信说:你司核心价值观有问题,改了之后再说吧

如何快速学习?

小程序只是增加了一种选择,以前是h5,pc,app,现在是小程序,h5,pc,app,它们无疑都是展示层的实现,所以对后台是没有影响的。它们的共性是会js能让你开发的更好,而今Node.js凭借其性能和强大npm生态,以及在大前端的火爆,使得Node.js无处不在。

Node全栈是一个比较好的方向,如果不想全栈,你至少要考虑前端

现代web开发里的大前端

Git 项目推荐 | 实现小程序 CNode 社区

Git 项目推荐 | 实现小程序 CNode 社区

Git 项目推荐 | 实现小程序 CNode 社区

Git 项目推荐 | 实现小程序 CNode 社区

更多的

高可用架构专用《全栈工程师之路-Node.js》 http://i5ting.github.io/nodejs-fullstack/

关于小程序未来

  • 开发组件定义
  • html转wxml,目前已经有人做了
  • 扫一扫
  • schema url open

推荐一些学习资料

参见

关于现状

Git 项目推荐 | 实现小程序 CNode 社区

  • 技术发展太快,是指数型发展
  • 人的学习速度是平滑的上升的曲线(学习到一定程度也是指数型上升的)
  • 持续学习,利用好时间,每日精进

作业

这个分类tab实现的太low了,很明显没有花费时间,如何通过class属性来实现一个更加好看的,带有选中状态的分类tab么?

大家可以自己试试

写在最后

  • 玩出乐趣,想想本文是怎么玩的?
  • 利用好时间:闲时要有吃紧的心思,忙里要有偷闲的乐趣
  • 少抱怨,多思考,未来更美好
  • 每日精进,自有成为大牛之日
分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址