weex官方weex-hackernews源码解读

一、weex-hackernews介绍

Weex官方基于Weex和Vue开发了一个的完整项目,在项目中使用了Vuex和vue-router,能够实现同一份代码,在 iOS、Android、Web 下都能完整地工作。weex-hackernews的项目地址

1、下载

下载地址:https://github.com/weexteam/weex-hackernews
直接下载zip包下来

2、运行weex-hackernews

2.1 准备:

     1.搭建Weex本地开发环境,可以前往Weex官方按照开发文档教程进行搭建搭建地址:http://weex.apache.org/cn/guide/set-up-env.html
     2.下载开发工具:WebStorm、AndroidStudio、Android SDK、CocoaPods

2.2 安装

安装依赖:

npm install

编译代码:

# 生成 Web 平台和 native 平台可用的 bundle 文件
# 位置:
# dist/index.web.js
# dist/index.web.js
npm run build

# 监听模式的 npm run build
npm run dev

拷贝bundle文件:

# 将生成的 bundle 文件拷贝到 Android 项目的资源目录
npm run copy:android

# 将生成的 bundle 文件拷贝到 iOS 项目的资源目录
npm run copy:ios

# run both copy:andriod and copy:ios
npm run copy

# 注意:window系统下,修改下package.json文件,copy:android对应的命令行,官网下载下来的是mac系统命令行,要进行修改
修改前
"copy:android": "cp dist/index.weex.js android/app/src/main/assets/index.js"
修改后:
"copy:android":"xcopy.\\dist\\index.weex.js.\\android\\app\\src\\main\\assets\\index.js"

启动Web服务

npm run serve

启动服务后监听1337端口,访问 http://127.0.0.1:1337/index.html 即可在浏览器中预览页面

启动Android项目

     启动Android项目,首先安装Android Studio和Android SDK,并配置好基本的开发环境;用Android Studio 打开 android 目录的项目,等待自动安装完依赖以后,即可启动模拟器或者真机预览页面;

启动 iOS 项目

     启动 iOS 项目,首先应该配置好 iOS 开发环境 并且安装 CocoaPods 工具;进入 ios 目录,使用 CocoaPods 安装依赖;

pod install

使用 Xcode 打开ios目录中的项目(HackerNews.xcworkspace),然后即可启动模拟器预览页面。

注:如果想要在真机上查看效果,还需要配置开发者签名等信息。

2.3 运行效果图
  • 首页

    这里写图片描述

  • 具体详情页

    这里写图片描述

二、代码分析

1  功能目录

将项目导入WebStorm里,功能目录分析

|-- android                          // android工程
|-- dist                             // android工程
|   |--dist/index.web.js             //Web平台bundle文件
|   |--dist/index.weex.js            //native平台bundle文件
|-- ios                              // ios工程
|-- src                              //项目的vue文件
|   |--components                    //vue组件(封装组件)
|   |--filters                       //vue的过滤器
|   |--mixins                        //vue的mixins(混合)
|   |--store                         //vuex(vue的状态管理器)
|   |--views                         //视图
|   |--App.vue                       //主UI界面
|   |--entry.js                      //入口文件
|   |--router.js                     //vue的路由声明

|-- .babelrc                         // ES6语法编译配置
|-- package.json                     // 配置项目相关信息,通过执行 npm init 命令创建
|-- qrcode.jpg                       //二维码
|-- README.md                        // 项目说明
|-- webpack.config.js                // 程序打包配置

2  vue路由router

2.1  vue-router 介绍

vue-router是vue.js官方支持的路由插件,它和vue.js是深度集成的,适合用于构建单页面应用。vue的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在vue-router单页面应用中,则是路径之间的切换,也就是组件的切换。

2.2  vue-router知识点

这里vue-router的知识点,这边就不进行阐述,因为官方上有详细的介绍:https://router.vuejs.org/zh-cn/,如果快速入手推荐看之前看过的文章:http://blog.csdn.net/sinat_17775997/article/details/52549123

2.2  项目路由分析

router.js文件

import Router from 'vue-router'
import StoriesView from './views/StoriesView.vue'
import ArticleView from './views/ArticleView.vue'
import CommentView from './views/CommentView.vue'
import UserView from './views/UserView.vue'

Vue.use(Router)

// Story view factory
function createStoriesView (type) {
  return {
    name: `${type}-stories-view`,
    render (createElement) {
      return createElement(StoriesView, { props: { type }})
    }
  }
}

export default new Router({
  // mode: 'abstract',
  routes: [
    { path: '/top', component: createStoriesView('top') },
    { path: '/new', component: createStoriesView('new') },
    { path: '/show', component: createStoriesView('show') },
    { path: '/ask', component: createStoriesView('ask') },
    { path: '/job', component: createStoriesView('job') },
    { path: '/article/:url(.*)?', component: ArticleView },
    { path: '/item/:id(\\d+)', component: CommentView },
    { path: '/user/:id', component: UserView },
    { path: '/', redirect: '/top' }
  ]
})
  • 首先导入Router
import Router from 'vue-router'
  • Vue注入router
Vue.use(Router)
  • router的路由配置,导入各种View
export default new Router({
  // mode: 'abstract',
  routes: [
    { path: '/top', component: createStoriesView('top') },
    { path: '/new', component: createStoriesView('new') },
    { path: '/show', component: createStoriesView('show') },
    { path: '/ask', component: createStoriesView('ask') },
    { path: '/job', component: createStoriesView('job') },
    { path: '/article/:url(.*)?', component: ArticleView },
    { path: '/item/:id(\\d+)', component: CommentView },
    { path: '/user/:id', component: UserView },
    { path: '/', redirect: '/top' }
  ]
})
  • router的入口路径是path’/’,这在后面分析App.vue会讲到。然而路由的路劲’/’重定向到’/top’,所以’/top’对应的文件才是真正App入口UI界面。
{ path: '/', redirect: '/top' }
  • 接着看对应着路径对应的’/top’对应的component.
{ path: '/top', component: createStoriesView('top') }

如果看完上面给的两个介绍router的链接,就知道path代表匹配路径,component对应组件的文件。可能大家都会问‘/top’对应明明是createStoriesView(‘top’),我们沿着createStoriesView(type)方法看下去:

function createStoriesView (type) {
  return {
    name: `${type}-stories-view`,
    render (createElement) {
      return createElement(StoriesView, { props: { type }})
    }
  }
}

其实会发现在‘/top’对应的文件是StoriesView,其中props对应是传入该组件的参数。

  • 路由跳转
    路由跳转有两种形式:声明和编程。
#声明:
<router-link :to="...">
#编程
router.push(...)
router.push({ path: 'home' }) 
router.push('home')
router.push({ name: 'user', params: { userId: 123 }})
//params跳转到组件传入参数key为userId,value为123
//跳转到的界面接收:this.$router.param.userId;

工程的路由的跳转封装在mixins/index.js文件中:

export default {
  methods: {
    jump (to) {
      if (this.$router) {
        this.$router.push(to)
      }
    }
  }
}

大家肯定疑问,mixins/index.js什么时候被注入到vue,能全局调用?其实在入口文件entry.js

// register global mixins.
Vue.mixin(mixins)

3  vue状态管理库Vuex

3.1  Vuex 介绍

Vuex是专为Vue.js应用程序开发的状态管理模型。它采用集中式存储应用的所有组件的状态(理想),并以相应规则保证状态已一种可预测的方式变化。Vuex也是集成到Vuex的官方调试工具

什么是”状态管理模式”?

包含以下几部分:

  • state,驱动应用的数据源
  • view,以声明方式将state映射到视图;
  • action,响应在view上的用户输入导致的状态变化

以下是一个表示“单向数据流”理念的示意

这里写图片描述

    Vuex的基本思想,借鉴Flu、Redux和The Elm Architechture,vuex是专门为Vue.js设计的状态管理库,利用响应体制来进行高效的状态更新。

Store
    每个Vuex的应用都有一个核心的store(仓库)。”store”是一个容器,里面存储应用的大部分的状态(state)。相当于一个全局对象,但是有跟全局对象有所区别的是,vuex的状态存储是响应式,只要store状态发生变化,那么相应的引用到的组件会跟着更新。
    store的核心内容由State、Getters、Mutations、Actions、Modules组成。

  • state:全局唯一数据源,定义着weex-hackernews工程的列表lists、用户users、items详情等数据;
  • getters:其实主要就是state数据处理,进行过滤操作;
  • mutation:唯一可以更改state里面的数据;
    -Actions:类似mutation,不同在于action提交的是mutation,而不是直接变更数据状态,Actions可以包含任意异步操作。

  • Modules:使用单一状态树,导致应用的所有状态集中到一个很大的对象。但是,当应用变得很大时,store 对象会变得臃肿不堪,Vuex 允许将 store 分割到模块(module),每个模块拥有自己的 state、mutation、action、getters.

        我们直接看下图官方的流程图,state作为全局数据源,我们通过dispatch触发action动作,action做业务处理在提交Mutation来改变State,State改变后自动Render到Vue的component组件上,从而实现单向数据流。
        看上面的理论大体懂个流程,有可能存在一知半解,后面直接通过项目的代码进行整个流程进行分析,到时基本可以明白Vuex的流程了。

4  入口文件

App.vue 文件

// import Vue from 'vue'
import App from './App.vue'                 //加载UI主界面
import router from './router'               //加载vue路由 
import store from './store'                 //加载vuex的store
import { sync } from 'vuex-router-sync'
import * as filters from './filters'        //加载vue的fitlter(过滤器)
import mixins from './mixins'                //加载vue的mixins(混合)

sync(store, router)

Object.keys(filters).forEach(key => {
  Vue.filter(key, filters[key])
})

Vue.mixin(mixins)

//vue扩展路由、状态管理器、入口UI主界面
new Vue(Vue.util.extend({ el: '#root', router, store }, App))             

router.push('/')     //默认路由跳转路径

该文件主要任务是路由(router)、状态管理器(store)、view的导入,
这边的路由的入口路径’/’

router.push('/')  

在router中的路由声明我们有提到过的路由的路口路径,就是在这边实现。

5  主界面

5.1  StoriesView主界面

StoriesView.vue
其实就是首页界面。
我们可以看到*.vue文件有三部分组成:<template>, <style>, <script> 构建

<template>
    .
    .
    .
</template>

<script>
    .
    .
    .
</script>

<style scoped>
    .
    .
    .
</style>

<template>必须的,主要是UI界面,使用 HTML 语法描述页面结构,内容由多个标签组成,不同的标签代表不同的组件。(weex限制范围内)
<script> 可选的,主要是业务逻辑,使用 JavaScript 描述页面中的数据和页面的行为(es6 的代码)
<style> 可选的,主要是样式,使用 CSS 语法描述页面的具体展现形式(weex限制范围内)
下面按照上面三部分解析StoriesView的代码:

  • script 业务逻辑
<script>
  //分别导入app-header、story组件  如果看不懂es6写法,建议先去看下补充下es6基础知识
  import AppHeader from '../components/app-header.vue'
  import Story from '../components/story.vue'

  export default {
    //声明组件   只有声明过的组件,在template才能使用,否则会报错
    components: { AppHeader, Story },
    //传参   我们可以看下之前路由router文件中提到的入参type传值,StoriesView的type就会被赋值
    props: {
      type: {
        type: String,            //type的数据类型
        required: true,          //必须元数
        default: 'top'           //默认值是top值
      }
    },
    //存放数据
    data () {
      return {
        loading: true
      }
    },
    //vue的计算属性
    computed: {
      //获取列表数据
      stories () {
        //从store获取数据
        return this.$store.getters.activeItems
      }
    },
    //使用方法
    methods: {
      //网络请求 列表接口数据
      fetchListData () {
        //加载状态 设置为显示
        this.loading = true
        //dispatch触发FETCH_LIST_DATA的action
        this.$store.dispatch('FETCH_LIST_DATA', {
          type: this.type
        }).then(() => {
          //加载状态 设置为隐藏
          this.loading = false
        })
      },
      //网络请求 加载更多>>列表接口数据
      loadMoreStories () {
        this.loading = true
        this.$store.dispatch('LOAD_MORE_ITEMS').then(() => {
          this.loading = false
        })
      }
    },
    //生命周期   组件实例创建完成,属性已绑定,但DOM还未生成
    created () {
      this.fetchListData()
    }
  }
</script>

fetchListData方法的里面使用到vuex,在此先不进行介绍,后面进行详说。

  • template UI界面
<template>
  <div class="stories-view" append="tree">
    <!--标题栏-->
    <app-header></app-header>
    <!--标题栏-->
    <!--list列表-->
    <list class="story-list" @loadmore="loadMoreStories" loadmoreoffset="50">
      <!--cell是list的item项 stories对应computed的stories() -->
      <cell class="story-cell" v-for="story in stories" :key="story.id" append="tree">
        <!--:story="story"  将数据story传参到story组件中-->
        <story :story="story"></story>
      </cell>
    </list>
    <!--list列表-->
    <!--加载更多控件-->
    <div class="loading" v-if="loading">
      <text class="loading-text">loading ...</text>
    </div>
    <!--加载更多控件-->
  </div>
</template>

标题栏

<app-header></app-header>

这里写图片描述

列表的item

<story :story="story"></story>

这里写图片描述

style 样式

<style scoped>
  .stories-view {
    height: 100%;
  }
  .story-cell {
    margin-bottom: 3px;
    border-bottom-width: 2px;
    border-bottom-style: solid;
    border-bottom-color: #DDDDDD;
    background-color: #FFFFFF;
  }
  .loading {
    width: 750px;
    height: 120px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .loading-text {
    margin: auto;
    text-align: center;
    font-size: 40px;
    color: #BBB;
  }
</style>

这边的样式就不详细介绍,直接官网直接看。

5.2  列表item

story.vue

<template>
    <div class="cell-item">
        <text class="story-score">{{story.score}}</text>
        <external-link :url="story.url" class="story-link">
            <text class="story-title">{{story.title}}</text>
            <text class="small-text" v-if="story.url">({{ story.url | host }})</text>
        </external-link>
        <div class="text-group">
            <text class="small-text text-cell">by</text>
            <!--jump 路由跳转-->
            <div class="text-cell" @click="jump(`/user/${story.by}`)">
                <text class="small-text link-text">{{story.by}}</text>
            </div>
            <text class="small-text text-cell"> | {{ story.time | timeAgo }} ago</text>
            <text class="small-text text-cell" v-if="!noComment"> |</text>
            <div class="text-cell" @click="jump(`/item/${story.id}`)" v-if="!noComment">
                <text class="small-text link-text">{{ story.descendants }} comments</text>
            </div>
        </div>
    </div>
</template>

<style scoped>
    .cell-item {
        position: relative;
        padding-top: 20px;
        padding-bottom: 25px;
        padding-left: 100px;
        padding-right: 40px;
    }

    .story-score {
        position: absolute;
        width: 100px;
        text-align: center;
        left: 0;
        top: 20px;
        font-size: 32px;
        font-weight: bold;
        color: #FF6600;
    }

    .story-link {
        margin-bottom: 25px;
        width: 610px;
    }

    .story-title {
        font-size: 33px;
        color: #404040;
    }

    .small-text {
        color: #BBB;
        font-size: 22px;
        margin-bottom: 0;
        font-family: Verdana, Geneva, sans-serif;
    }

    .link-text {
        /*color: red;*/
        text-decoration: underline;
    }

    .text-group {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: flex-start;
        align-items: center;
    }

    .text-cell {
        flex-grow: 0;
    }
</style>

<script>
    //导入控件
    import ExternalLink from './external-link.vue'

    export default {
        //控件声明
        components: {ExternalLink},
        //参数   列表的item数据
        props: {
            story: {
                type: Object,
                required: true
            },
            'no-comment': {
                type: [String, Boolean],
                default: false
            }
        }
    }
</script>

在template可以看到路由跳转方法jump

<div class="text-cell" @click="jump(`/user/${story.by}`)">

jump其实调取的是mixins(混合)文件夹下index.js的jump方法

this.$router.push(to)路由的跳转,to参数是对应的配置路径,会对应这router.js的路由表跳转到对应的vue;

5.3  标题栏

app-header.vue

<template>
  <div class="header">
    <!--@click="jump('/')"  点击事件>>>路由跳转:路径‘/’-->
    <div class="logo" @click="jump('/')">
      <!--src加载网络图片 地址:https://news.ycombinator.com/favicon.ico-->
      <image class="image" src="https://news.ycombinator.com/favicon.ico"></image>
    </div>
    <div class="nav">
      <div class="link" @click="jump('/top')">
        <text class="title">Top</text>
      </div>
      <div class="link" @click="jump('/new')">
        <text class="title">New</text>
      </div>
      <div class="link" @click="jump('/show')">
        <text class="title">Show</text>
      </div>
      <div class="link" @click="jump('/ask')">
        <text class="title">Ask</text>
      </div>
      <div class="link" @click="jump('/job')">
        <text class="title">Job</text>
      </div>
    </div>
  </div>
</template>

<style scoped>
  .header {
    position: relative;
    height: 120px;
    margin-bottom: 3px;
    border-bottom-width: 2px;
    border-bottom-style: solid;
    border-bottom-color: #DDDDDD;
    background-color: #FF6600;
  }
  .logo {
    position: relative;
    width: 50px;
    height: 50px;
    top: 35px;
    left: 35px;
    border-width: 3px;
    border-style: solid;
    border-color: #FFFFFF;
  }
  .image {
    width: 44px;
    height: 44px;
  }
  .nav {
    display: flex;
    position: absolute;
    left: 120px;
    top: 35px;
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: flex-start;
    align-items: center;
  }
  .link {
    padding-left: 15px;
    padding-right: 15px;
  }
  .title {
    font-family: Verdana, Geneva, sans-serif;
    font-size: 32px;
    line-height: 44px;
    color: #FFFFFF;
  }
</style>

其他view就不再一一分析,都是大同小异。

6  数据store工程整体流程

6 .1 数据store工程整体流程

我们从一开始导入Vuex入手:

在entry入口文件

import store from './store

导入store,接着我们看下store的文件夹下的index.js文件。

首先,导入vuex插件

import Vuex from 'vuex

判断是否移动平台,是移动平台,将vuex插件导入vuex

if (WXEnvironment.platform !== 'Web') {
  Vue.use(Vuex)
}

而后实例化Store:

onst store = new Vuex.Store({
  actions,
  mutations,

  state: {
    activeType: null,
    items: {},
    users: {},
    counts: {
      top: 20,
      new: 20,
      show: 15,
      ask: 15,
      job: 15
    },
    lists: {
      top: [],
      new: [],
      show: [],
      ask: [],
      job: []
    }
  },

  getters: {
    activeIds (state) {
      const { activeType, lists, counts } = state
      return activeType ? lists[activeType].slice(0, counts[activeType]) : []
    },
    activeItems (state, getters) {
      return getters.activeIds.map(id => state.items[id]).filter(_ => _)
    }
  }
})

new Vuex.Store单例模式,里面分别注入state、actions、mutations、getters模块;state存储全局唯一数据源,定义着工程的列表lists、用户users、items详情等数据;
acitons和mutations分别在action.js和mutations文件中。
要理解vuex,我们直接拿获取列表数据做实例讲解:

网络获取数据赋值到store上

在StoriesView.vue文件

fetchListData () {
        this.loading = true
        this.$store.dispatch('FETCH_LIST_DATA', {
          type: this.type
        }).then(() => {
          this.loading = false
        })
      }

this.$store.dispatch(‘FETCH_LIST_DATA’,{type: this.type})触发actions里面的FETCH_LIST_DATA的动作,并传参数type值。

在actions.js文件

//第一个参数是store参数   第二个参数type是dispatch传参
export function FETCH_LIST_DATA ({ commit, dispatch, state }, { type }) {
  commit('SET_ACTIVE_TYPE', { type })
  return fetchIdsByType(type)
    .then(ids => commit('SET_LIST', { type, ids }))
    .then(() => dispatch('ENSURE_ACTIVE_ITEMS'))
}

第四行是调用mutation的SET_ACTIVE_TYPE方法,进行activeType的赋值;

export function SET_ACTIVE_TYPE (state, { type }) {
  state.activeType = type
}

继续看下fetchIdsByType()方法,fetchIdsByType()方法其实是从fetch文件导入

import { fetchItems, fetchIdsByType, fetchUser } from './fetch'

接着看fetchIdsByType()调取是fetch

export function fetchIdsByType (type) {
  return fetch(`${type}stories`)
}

接着往下看,fetch是进行网络接口请求,weex中通过stream提供网络访问共鞥,通过stream.fetch获取,这边我们发现fetch函数返回时一个Promise对象,关于Promise是es6,大家可以自己查阅下,这里不进行阐述。

export function fetch (path) {
  //异步请求
  return new Promise((resolve, reject) => {
    stream.fetch({
      //请求方式 
      method: 'GET',
      //请求地址
      url: `${baseURL}/${path}.json`,
      //数据类型
      type: 'json'
    }, (response) => {
      if (response.status == 200) {
        //请求成功 进行成功回调
        resolve(response.data)
      }
      else {
      //请求失败 进行失败回调
        reject(response)
      }
    }, () => {})
  })
}

我们再回过头看下之前获取列表请求的方法

export function FETCH_LIST_DATA ({ commit, dispatch, state }, { type }) {
  commit('SET_ACTIVE_TYPE', { type })
  return fetchIdsByType(type)
    .then(ids => commit('SET_LIST', { type, ids }))//请求成功
    .then(() => dispatch('ENSURE_ACTIVE_ITEMS'))//请求失败
}

第4行是请求成功回调方法,调取multation的SET_LIST方法并进行数据列表lists赋值;
第5行请求失败调取actions的ENSURE_ACTIVE_ITEMS方法;
我们接着看commit调取mutations的SET_LIST类型函数

export function SET_LIST (state, { type, ids }) {
  state.lists[type] = ids
}

在SET_LIST函数中对store中state的lists进行赋值;

UI上填充数据

在StoriesView.vue文件

 <cell class="story-cell" v-for="story in stories" :key="story.id" append="tree">
        <!--:story="story"  将数据story传参到story组件中-->
        <story :story="story"></story>
      </cell>

stories数据调取是script模块函数

 computed: {
      stories () {
        return this.$store.getters.activeItems
      }
    }

this.$store.getters.activeItems调取是store里面的getters模块的activeItems函数:

activeItems (state, getters) {
      return getters.activeIds.map(id => state.items[id]).filter(_ => _)
    }

其实调取函数activeIds

activeIds (state) {
      const { activeType, lists, counts } = state
      return activeType ? lists[activeType].slice(0, counts[activeType]) : []
    }

activeIds 主要做的事过滤store的state模块的lists数据,返回数据是activeType类型文章的lists数据。

三、个人见解

    1、如果只是单纯只是开发单界面,不用考虑工程里面嵌入router和vuex,毕竟只是单个界面不存在路由跳转复杂逻辑和大量的状态管理;
    2、如果开发app项目大部分界面使用weex,那么可以优先考虑嵌入router和vuex,实际项目会很多组件需要维护state状态维护;
    3、开发中的es6语法糖对于移动端开发者,在遇到时候再去看相应的资料,不建议一头扎进es6语法;
    4、项目中的vuex和router知识很重要;
    5、尽管weex-hackernews的没有进行store没有进行模块划分,实际项目建议根据项目需求进行划分。

wee官网                    http://weex.apache.org/cn/
vue官网                     https://cn.vuejs.org
vue-router官网          https://router.vuejs.org/zh-cn/
Vuex官网                   https://vuex.vuejs.org/zh-cn/
大灰狼的小绵羊哥哥的vue-router 60分钟快速入门          
http://blog.csdn.net/sinat_17775997/article/details/52549123

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Hacker News本身也是开源的,为什么还要做iNews?初衷是什么? 赵戈戈:Hacker News从功能和内容本身上来说是个很好的应用,我们最初也是想从开源入手 —— 直接使用,不过HN是甚于Arch实现的,我们对Arch 并不熟悉,所以决定自己写,以便功能扩展。 关于初衷。从产品上讲,要从我和搭档 @sofish说起。我们都是苹果的忠实用户,经常会互相分享一些应用,久而久之也就萌发了这个念头:做一个分享苹果新闻和应用的社区,对于苹果在中国这几年设备数据大增,应该有很多人跟我们有同样的需求,于是就开始做了;从技术上讲,它的结构类似于Hacker News,而外观应该更漂亮,并且要适合移动应用阅读。最终我们动手实现了它,并上线了http://inews.io。虽然现在还没有完全做到期望值,但最起码迈出了第一步;从程序本身来讲,上线了一段时间后,很多朋友看到都希望在自己站点上使用,后来越来越多的人寻求使用并提供建议,基于我们对开源的开放态度,最终决定放出来给大家使用。 iNews 在技术上是如何实现的? 赵戈戈:iNews基于PHP,使用Mysql存储,phpmig进行数据库版本管理。框架上,后端采用了我自己写的Restful框架 Pagon来开发,前端使用了@sofish的 typo.css, validator.js等开源库。第一版本上线前我们希望从最基本的功能开始,上线 > 快速迭代。整个开发过程非常快,只用了一天半的时间就实现了。后续做了些优化和修复就上线了。在服务上我们也采用一些不错的第三方服务:用SendGrid发送邮件,用NewRelic做应用监控,用Dropbox做数据库备份等等。 这里再补充一点:从技术选型上,技术适应产品才是最重要的,不要强迫自己使用的是PHP 、Node或者Ruby等。选择基于PHP的Pagon这个框架,让我们可以更快速实现iNews.io的功能。 总共有几名开发人员?这种UGC项目的技术难点和重点是什么?你们是如何解决的? 赵戈戈:只有我和搭档@sofish两个人。开源之后应该会陆续会有一些同学加入,实际上目前已经有一些同学在一起开发。对于iNews来说,技术上基本没太多难点,主要有几个点需要关注:安全性、性能、易用、适配(Responsive)、社会化接入等。 安全性主要是XSS和数据库过滤。Pagon框架已实现自动XSS处理的,只要开启了safe_query 的选项,在模板渲染的过程中会自动做XSS过滤。数据库方面使用的一个paris库,它本身使用PDO来处理SQL,PDO本身会对数据进行转义处理再存入。 性能其实是个很泛的话题,iNews.io数据样本太少,还不好看出性能到底如何,就目前的平均输出时间来看只有30ms左右,前端方面使用Google Pagespeed测试是:移动,85/100;桌面 95/100;这比很多网站分数都要高。高性能跟逻辑简单和实现方法都有关系,如果需要很好的性能需要考虑到方方面面,我这里先简单说PHP的基本观点:代码越简单越好,不要使用太多魔术方法,尽量使用PHP 内置的方法,使用最新的稳定版5.4,服务器安装APC扩展,使用Nginx FPM模式运行PHP。其次Pagon框架本身开发过程也对性能有很高的要求,所以在代码质量和性能上也下了不少功夫,这也是应用性能的基础。在数据库方面只做了索引优化来达到更好的查询性能。性能还涉及到很多,做为一个刚初出茅庐的应用来说还没有足够完善也不需要把性能放到第一位,在这里就先不说还没有做到的。 而在前端,跨终端适配应该是现代应用最应该做的支持。@sofish 在这一块有很多的研究,所以我们的 iNews 是可以在移动设备上很好的访问的。 社会化接入这块其实也并不是什么难点,只需要有很好的封装就可以解决问题,重新开发代价稍大。我觉得 PHP 第三方认证的库都做的很一般,但还是有个opauth的第三方认证库,封装有点凌乱,但凑合着能用。如果是Node的话,这一块有很多很不错的库,比如everyauth和passport,如果PHP社区可以像Node社区一样活跃那问题就可以更好解决了。 iNews在内容推荐上采用了什么样的机制或算法? 赵戈戈:iNews最初是参照Hacker News的算法来做的,后期将评论也加入了权重。因为觉得对于这种社区来说,评论是很重要的一块,评论代表着交流,有交流就证明内容的价值有可能存在,如果只有“顶”才算权重,那激烈的讨论可能会显得不太公平。但若是每个评论的权重和每个“顶”的权重一样,又显得不太合理,“顶”是认可的意思,评论是交流的意思,所以评论应该稍低于“顶”的权重,这样做才比较合理。最终是这么来做的。 iNews 有哪些创新性的东西? 赵戈戈:如果说创新的话,我想提一个设计模式:MOVE。应该有不少人看到过过这个设计模式,摒弃了MVC传统老套的做法。采用了Model、Operator、View 和 Event 来梳理编码流程。其中 Operator可能是大家最不解的一个地方,其实也是最具创新意义的地方,Opeartor从字面意思来理解就是:操作器。Opeartor的出现摈弃了控制器重用性和代码混杂的局面,将每个操作都拆分成一个独立的操作器,组织成一个树形的流程来封装使用,对于编码流程来说是一个梳理。iNews里面有一些对操作器的浅显理解和实现,但并没有完全使用操作器来做,还是主要使用MVC作为基础,这种尝试会慢慢变得成熟,并可能逐步完全采用这种模式来开发。 标签:iNews

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值