weex官方weex-hackernews源码解读
来源:互联网 发布:淘宝客域名备案 编辑:程序博客网 时间:2024/06/07 14:29
一、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.jsnpm run build# 监听模式的 npm run buildnpm run dev
拷贝bundle文件:
# 将生成的 bundle 文件拷贝到 Android 项目的资源目录npm run copy:android# 将生成的 bundle 文件拷贝到 iOS 项目的资源目录npm run copy:ios# run both copy:andriod and copy:iosnpm 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 factoryfunction 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的storeimport { 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
- weex官方weex-hackernews源码解读
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(下)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- weex官方demo weex-hackernews代码解读(1)
- Weex
- weex
- WEEX
- weex
- Weex Android SDK源码分析
- Weex 如何快速运行官方的examples?
- weex源码浅析(Android部分)一
- APRIORI算法详解和python代码
- DuiVision开源界面库介绍
- java8新特性整理
- 部署storm和配置文件详解
- 【XJOI】path 题解
- weex官方weex-hackernews源码解读
- erlang mnesia 节点同步数据
- Linux写时拷贝技术(copy-on-write)
- 操作系统大集合-那些消失的操作系统
- 路径压缩
- 优化iOS程序性能的25个方法
- 分点新技能+网络流
- Java与算法(5)
- 漫画说算法--动态规划算法一(绝对通俗易懂,非常棒)