# 零基础学开发，她做了款「仿知乎」小程序 \| 实战教程

- 作者：知晓程序
- 发布时间：2016-12-05 18:59
- 原文：https://www.ifanr.com/minapp/751058

![zhihu](https://s3.ifanr.com/wp-content/uploads/2016/12/zhihu.jpg)

有多少人是零基础，又想学小程序开发呢？

今天，知晓程序（微信号 zxcx0101）给大家分享一个励志案例：一个读大气科学、自学前端开发的妹子，因为喜欢逛知乎，自己做出了一款「仿知乎」小程序。

希望她的开发经验，能帮大家轻松愉悦地迈出小程序开发的第一步。

如果你想和本文作者进行交流，关注 zxcx0101，点击菜单栏的「**加群交流**」即可。

---

作者 \| Rebecca Han  
WEB 前端/不靠谱天气预报员/想做代码小仙女

从微信小程序开始内测到现在，我终于把自己的微信小程序 demo 完成了。

> 真的是墨迹完的，连我自己都佩服自己的拖延症了（懒癌少女已弃疗 ヾ（´A｀）ノﾟ） 总之，算是基本完成了（明明有很多组件啊、API 啊根本都没用好嘛 →\_→）以及从来不写 blog 的我也出来码字啦 （ﾉ◕ヮ◕）ﾉ:･ﾟ✧

之前有很长一段时间，我算是知乎重度依赖。所以，这次 demo 的模仿对象选择的是知乎（但是写到一半发现我这个决定坑了，这是后话）。

Demo 的界面设计以及交互设计，均来自于知乎 Android 版本。

- **工具：**使用的是微信 web 开发者工具，这个工具已经全面对非邀请内测用户开放，且在持续更新中（我码代码的过程中就更新了两版，所以开发时 IDE 版本不唯一）。其实，忍受了半个小时微信的开发者工具之后，我就改在 Webstorm 中编辑了，微信工具成了运行预览的工具。不过听说 IDE 中预览的效果，也不能保证与真机一样哦~
- **设计和功能：**参照的是知乎安卓版本非常之简易版，为了防止版权问题，Demo 中的 fake 数据使用的是我自己的回答。

### 文件构建

**1. 基础文件**

app.json:

```
{
  "pages": [
    "pages/index/index", 
    "pages/discovery/discovery",
    "pages/notify/notify",
    "pages/chat/chat",
    "pages/more/more",
    "pages/answer/answer",
    "pages/question/question"

  ], 
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#0068C4",
    "navigationBarTitleText": "知乎",
    "navigationBarTextStyle": "white",
    "enablePullDownRefresh": true
  }, 
  "tabBar": {
    "color": "#626567",
    "selectedColor": "#2A8CE5",
    "backgroundColor": "#FBFBFB",
    "borderStyle": "white"
    "list": [{
      "pagePath": "pages/index/index",
      "text": "",
      "iconPath": "images/index.png",
      "selectedIconPath": "images/index_focus.png"
    }, {
      "pagePath": "pages/discovery/discovery",
      "text": "",
      "iconPath": "images/discovery.png",
      "selectedIconPath": "images/discovery_focus.png"
    }, {
      "pagePath": "pages/notify/notify",
      "text": "",
      "iconPath": "images/ring.png",
      "selectedIconPath": "images/ring_focus.png"
    }, {
      "pagePath": "pages/chat/chat",
      "text": "",
      "iconPath": "images/chat.png",
      "selectedIconPath": "images/chat_focus.png"
    }, {
      "pagePath": "pages/more/more"
      "text": ""
      "iconPath": "images/burger.png"
      "selectedIconPath": "images/burger_focus.png"
    }]
  }, 
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  },
  "debug": true
}

```

`app.jso` `n` 文件中是对整个小程序的全局配置，**主要用到的字段有**`pages`**，**`window`**，**`tabBar`**，**`networkTimeout`。

- `pages` **字段：**所有小程序的页面都要在该字段中注册，该字段数组中的第一个 page 默认为小程序首页（设置 tab 除外），没有在 pages 字段注册过的页面貌似不能够进行有效的编译（之前版本的编辑器没有这个限制，只是会影响配置文件等的生效，编辑器更新后会报未注册的错误）。
- `window` **字段：**大多是关于小程序顶部 navigationbar 的一些设置。
- `tabBar` **字段：**如果你需要首页底部带 tabbar 的样式，那么就在 tabBar 字段中设置每个 tab 对应的页面，按顺序对应左至右，包括路径、tab 文字、tab 图标和选中状态图标。
- `networkTimeout`：设置网络超时时间。
- `d` `ebug`：开启 debug 模式。

`app.wxss` 文件中为全局样式，也就是说**这个文件中的样式在所有的 pages 中均可使用**。若其他页面文件的 WXSS 中定义了与该样式文件中相同的属性，则该文件中的样式被覆盖，规则与 CSS 优先规则大致相通。`app.js` 用于调用 `login` 接口、回调、周期函数、本地存储等等逻辑代码。**2. 页面文件**

![zhihu-1](https://s3.ifanr.com/wp-content/uploads/2016/12/zhihu-1.jpg)

页面文件由四部分组成。

例如，我们有一个首页叫做 `index`，则需要在 `pages` 文件夹下创建文件名相同的三个必要文件:

- `index.wxml`
- `index.wxss`
- `index.js`

另外 `index.json` 文件为可选，功能与 `app.json` 相同，为该页面的配置文件，但定义功能有限。

**3. UI**

跟平时开发一样，最开始当然是码 UI。

除了需要依照微信的一些新的标签和样式规则，其他与平时码 UI 并没有太大的不同。

而且需要强调的是，**`flex` 布局在微信小程序中 hin~~~~好用**

不过，同时作为女生和程序员，不挑刺可就不是我了，所以**下面列举了一些我遇上的坑**，其中有些也许不正确（多多包涵啦\\(//∇//)\\），有些也许已在 IDE 更新中修正：

1. 有一些 CSS 样式在微信 IDE 中不支持，例如 `font-weight`、`letter-spacing`（以及调整字间距的样式）等；
2. `<text/>` 不支持嵌套，`<text/>` 两层嵌套的结构下，内层中的内容会连续显示两次（在 IDE 后续更新中已修正）；
3. 若 `<` `view/>` 与 `<text/>` 同级，则在实际使用中，`<view />` 会遮住 `<text />`。为了防止内容相叠，必须使用 `<view/>` 相并列。**所以并不像某些地方说的，可以完全地把** `<view/>` **当做** `<div>` **去使用！**
4. 元素之前有垂直相邻 margin 的时候，在微信小程序中会 double 显示，即两个元素的 margin 均摊开，不遵循 margin 折叠规则；
5. 标签 `hidden` 属性无效（v0.10.101400 中已修正）；
6. 部分情况下，平级元素标签优先级会出现问题。比如， 平级标签 A 与 B，当 B 通过某些调整向 A 元素位置相叠的时候，微信 IDE 解析出的效果是 A 的内容和背景色会覆盖 B 元素与之重叠的部分（普通浏览器解析应该是 B 覆盖 A）；
7. 如果用模板+列表渲染的方式来渲染数据的话，在模板中使用列表渲染的 `{{item}}` 是无效的，无法被正确识别。所以，列表渲染的时候，要把复用的部分写在列表渲染的代码块内（属于数据渲染部分，后面会提到）。

在后来，微信 IDE 的迭代中已经将上面清单中的一些问题修正了，所以这份清单有可能是过时的。大家也可以根据实际情况，自行尝试。

接下来，我将对于一些 demo 中写到用到的部分进行说明。

### 开始实战开发

**1. 列表式的数据渲染**

![zhihu-2](https://s3.ifanr.com/wp-content/uploads/2016/12/zhihu-2.jpg)

类似于「首页」以及「发现页」这种标准列表式的数据展现方式，**微信提供了很好的方案：列表渲染。**

```
<block wx:for="{{feed}}" wx:for-index="idx" wx:for-item="item" data-idx="{{idx}}">
  <view class="feed-item">
    <view class="feed-source">
      <a class="">
        <view class="avatar">
          <image src="{{item.feed_source_img}}"> </image>
        </view>
        <text> {{item.feed_source_name}} {{item.feed_source_txt}} </text>
      </a>
      <image class="item-more" mode="aspectFit" src="../../images/more.png"></image>
    </view>
    <view class="feed-content">
      <view class="question" qid="{{question_id}}" bindtap="bindQueTap">
        <a class="question-link">
          <text>{{item.question}}</text>
        </a>
      </view>
      <view class="answer-body">
        <view bindtap="bindItemTap">
          <text class="answer-txt" aid="{{answer_id}}"> {{item.answer_ctnt}} </text>
        </view>
        <view class="answer-actions" bindtap="bindItemTap">
          <view class="like dot"> <a>{{item.good_num}} 赞同 </a> </view>
          <view class="comments dot"> <a>{{item.comment_num}} 评论 </a> </view>
          <view class="follow-it"> <a>关注问题</a> </view>
        </view>
      </view>
    </view>
  </view>
</block>

```

可以直观地看出，就是 `for` 循环用重复的结构渲染一组数据：

- `for="{{}}`“中的内容是想要循环的一组数据，最外层为数组结构
- `for-item` 指定数组中当前元素的变量名
- `for-index` 指定数组中当前元素下标变量名

同样也使用了 `for` 渲染的，还有顶部的发现页和通知页等顶部的自定义 tabbar。

**2. 顶部 Tabbar 实现**

![zhihu-4](https://s3.ifanr.com/wp-content/uploads/2016/12/zhihu-4.jpg)

微信只提供了底部 tabbar，所以顶部的要自己写喽~

顶部 tab bar 的实现在于 `for` 列表渲染以及 JS 配合。

实例 WXML 代码：

```
<view class="top-tab flex-wrp flex-tab ">
  <view class="toptab 
        flex-item {{currentNavtab==idx ? 'active' : ''}}" wx:for="{{navTab}}" wx:for-index="idx" wx:for-item="itemName" data-idx="{{idx}}" bindtap="switchTab"> {{itemName}} </view>
</view>
<scroll-view scroll-y="true" class="container discovery withtab" bindscrolltoupper="upper" bindscrolltolower="lower" scroll-into-view="{{toView}}" scroll-top="{{scrollTop}}">
  <view class="ctnt0" hidden="{{currentNavtab==0 ? '' : true}}"> </view>
  <view class="ctnt1 placehold" hidden="{{currentNavtab==1 ? '' : true}}">
    <text>圆桌</text>
  </view>
  <view class="ctnt2 placehold" hidden="{{currentNavtab==2 ? '' : true}}">
    <text>热门</text>
  </view>
  <view class="ctnt3 placehold" hidden="{{currentNavtab==3 ? '' : true}}">
    <text>收藏</text>
  </view>
</scroll-view>

```

JS 代码：

```
//discovery.js
Page({
  data: {
    navTab: ["推荐", "圆桌", "热门", "收藏"]
    , currentNavtab: "0"
  }
  , onLoad: function () {
    console.log('onLoad')
  }
  , switchTab: function (e) {
    this.setData({
      currentNavtab: e.currentTarget.dataset.idx
    });
  }
});

```

由于微信不支持使用 DOM 访问对象，也就是不支持 `dom` 和 `window` 对象，所以 tab bar 的实现依赖于微信提供的视图层的展示逻辑，以及视图与数据之间的绑定机制。

绑定点击事件，通过改变一个 `data-` 属性的值，来控制元素的类的改变（从而改变样式等）。

**3. 轮播图**

以下是实现的 WXML 代码：

```
<swiper class="activity" indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}">
  <block wx:for="{{imgUrls}}">
    <swiper-item>
      <image src="{{item}}" class="slide-image" width="355" height="155" /> 
    </swiper-item>
  </block>
</swiper>

```

JS 代码：

```js
imgUrls: [
  '../../images/24213.jpg'
  , '../../images/24280.jpg'
  , '../../images/1444983318907-_DSC1826.jpg'
]
  , indicatorDots: false
  , autoplay: true
  , interval: 5000
  , duration: 1000
  , feed: []
  , feed_length: 0

```

轮播图的实现使用的是微信提供的 swiper 组件，该组件贴心的提供了各种属性选择，常用的包括 `autoplay`，interval 时间 `duration` 等

`swiper-item` 中包含的是所有轮播的图片，为了方便修改图片数据，同样采用 `for` 渲染绑定 `data` 的方式。

**4. 下拉刷新，上拉加载，以及数据请求**

刷新及继续加载的动作，依靠的是 `scroll-view` 标签，及配套的 `upper` 和 `lower` 事件。

标签的属性提供了 `bindscrolltoupper` 和 `bindscrolltolower` 来绑定滚动到顶部及底部所触发的事件，同时 `upper-threshold` 和 `lower-threshold` 能够调整触发时距边界的距离。

除上述之外，**小程序的 API 还提供横向滚动、滚动触发事件、设置滚动条位置等接口**。

```
<scroll-view scroll-y="true" class="container" bindscrolltoupper="upper" upper-threshold="10" lower-threshold="5" bindscrolltolower="lower" scroll-into-view="{{toView}}" scroll-top="{{scrollTop}}">
  <block wx:for="{{feed}}" wx:for-index="idx" wx:for-item="item" data-idx="{{idx}}">
    <view class="feed-item">
      <view class="feed-source">
        <a class="">
          <view class="avatar">
            <image src="{{item.feed_source_img}}"> </image>
          </view>
          <text> {{item.feed_source_name}} {{item.feed_source_txt}} </text>
        </a>
        <image class="item-more" mode="aspectFit" src="../../images/more.png"> </image>
      </view>
      <view class="feed-content">
        <view class="question" qid="{{question_id}}" bindtap="bindQueTap">
          <a class="question-link">
            <text>{{item.question}}</text>
          </a>
        </view>
        <view class="answer-body">
          <view bindtap="bindItemTap">
            <text class="answer-txt" aid="{{answer_id}}"> {{item.answer_ctnt}} </text>
          </view>
          <view class="answer-actions" bindtap="bindItemTap">
            <view class="like dot"> <a>{{item.good_num}} 赞同 </a> </view>
            <view class="comments dot"> <a>{{item.comment_num}} 评论 </a> </view>
            <view class="follow-it"> <a>关注问题</a> </view>
          </view>
        </view>
      </view>
    </view>
  </block>
</scroll-view>

```

滚动至顶或至底时，触发的加载数据的事件，本应该调用微信提供的网络请求 API 来获取数据。

但是比较坑的是，**我在选择写仿知乎 demo 的时候没有注意到知乎不提供开放 API，而微信的 API 不支持直接对 JSON 文件进行本地请求。**

无奈之下，选择在 JS 文件中伪造一段数据，用 `module.exports` 抛出，来 fake 数据请求：

```

upper: function () {
  wx.showNavigationBarLoading()
  this.refresh();
  console.log("upper");
  setTimeout(function () {
    wx.hideNavigationBarLoading();
    wx.stopPullDownRefresh();
  }, 2000);
}, lower: function (e) {
  wx.showNavigationBarLoading();
  var that = this;
  setTimeout(function () {
    wx.hideNavigationBarLoading();
    that.nextLoad();
  }, 1000);
  console.log("lower")
}, //scroll: function (e) {
//  console.log("scroll")
//},
//网络请求数据, 实现刷新
refresh0: function () {
  var index_api = '';
  util.getData(index_api).then(function (data) {
    //this.setData({
    //
    //});
    console.log(data);
  });
}, //使用本地 fake 数据实现刷新效果
refresh: function () {
  var feed = util.getDiscovery();
  console.log("loaddata");
  var feed_data = feed.data;
  this.setData({
    feed: feed_data
    , feed_length: feed_data.length
  });
}, //使用本地 fake 数据实现继续加载效果
nextLoad: function () {
  var next = util.discoveryNext();
  console.log("continueload");
  var next_data = next.data;
  this.setData({
    feed: this.data.feed.concat(next_data)
    , feed_length: this.data.feed_length + next_data.length
  });
}

```

由于是 fake 的数据，所以这个 demo 并没有做真实的带参跳转，查询等功能。

加载数据的同时，使用微信提供的加载动画 `wx.showNavigationBarLoading();`。

![zhihu-5](https://s3.ifanr.com/wp-content/uploads/2016/12/zhihu-5.jpg)

**5. 其他**

- 为了实现点击跳转页面，使用了 `wx.navigateTo` 进行页面跳转以及点击事件绑定；
- 部分常用组件的模块化；
- `input`、`image` 组件等的使用。

 **6. 后续**

其实还有大量的组件和 API 还没有用过，这个 demo 也许后续还会有更新呦，这取决于懒癌少女的病情严重程度了。

### 一点感受

其实作为一个小前端，由于工作中的原因，使用 MVVM 其实是非常少的。

不过，写了这个微信小程序 demo 之后，更加把这方面的思维理顺了。当然，写完之后回头看，还是有超多的不足，明明好些地方能再换一种写法的。

这毕竟是我第一次尝试用新鲜热乎的东西写小 demo，也是第一次尝试写教程……或者算是记录？whatever~~

Anyway~ 希望除了写代码之外，还能在码文字的道路上也多走走吧，毕竟我是要做代码小仙女的人呀\\（≧∀≦）ゞ

GitHub 项目地址：GitHub – RebeccaHanjw/weapp-wechat-zhihu

文章来源：<https://zhuanlan.zhihu.com/p/23394858>

**本文由知晓程序授权转载，关注微信号 zxcx0101，回复「电商」获得电商类小程序的开发教程文章。**

快速关注知晓程序↓↓↓

![zxcx](https://s3.ifanr.com/wp-content/uploads/2016/11/zxcx-1.jpg)  
**知晓程序（微信号 zxcx0101）是爱范儿旗下专注于小程序生态的公众号。我们提供最全面、新鲜的小程序资讯（消息、观点、指南、活动）和服务，在这里你能了解到关于小程序的一切。**
