地理定位博客开发全解析
1. Vue渲染函数基础
1.1 createElement函数
在Vue中,
createElement
函数用于创建元素,它可以是DOM元素或Vue组件。该函数最多接受三个参数:
-
element(必需)
:可以是HTML标签名、已注册组件的ID或直接是组件定义对象,也可以是返回这些内容的函数。
-
data(可选)
:数据对象,用于指定CSS类、props、事件等信息。
-
children(可选)
:可以是文本字符串或使用
createElement
构建的子元素数组。
通常,我们会使用
h
作为
createElement
的别名,这是大家常用的名称,也是JSX所要求的。
h
源自 “使用JavaScript编写HTML” 的超文本标记概念。
以下示例等价于
<template><p class="content">{{ message }}</p></template>
:
render (h) {
return h('p', { class: 'content' }, this.message)
}
1.2 动态模板
直接编写渲染函数的主要优点是它们更接近编译器,并且可以充分利用JavaScript的能力来操作模板。不过,明显的缺点是代码不再像HTML,不过这可以通过JSX来缓解。
例如,创建一个可以渲染任意级别的标题组件:
Vue.component('my-title', {
props: ['level'],
render (h) {
return h(
`h${this.level}`,
this.$slots.default
)
}
})
使用示例:
<my-title level="2">Hello</my-title>
若使用模板实现则会比较繁琐:
<template>
<h1 v-if="level === 1">
<slot></slot>
</h1>
<h2 v-else-if="level === 2">
<slot></slot>
</h2>
<h3 v-else-if="level === 3">
<slot></slot>
</h3>
<h4 v-else-if="level === 4">
<slot></slot>
</h4>
<h5 v-else-if="level === 5">
<slot></slot>
</h5>
<h6 v-else-if="level === 6">
<slot></slot>
</h6>
</template>
1.3 数据对象
数据对象是
createElement
的第二个可选参数,用于传递关于元素的额外信息。以下是一个涵盖大部分特性的数据对象示例:
{
'class': {
foo: true,
bar: false
},
style: {
color: 'red',
fontSize: '14px'
},
attrs: {
id: 'foo'
},
props: {
myProp: 'bar'
},
domProps: {
innerHTML: 'baz'
},
on: {
click: this.clickHandler
},
nativeOn: {
click: this.nativeClickHandler
},
directives: [
{
name: 'my-custom-directive',
value: '2',
expression: '1 + 1',
arg: 'foo',
modifiers: {
bar: true
}
}
],
slot: 'name-of-slot',
key: 'myKey',
ref: 'myRef'
}
例如,根据标题级别应用特殊CSS类:
Vue.component('my-title', {
props: ['level'],
render (h) {
return h(
`h${this.level}`,
{
'class': {
'important-title': this.level <= 3
}
},
this.$slots.default
)
}
})
添加点击事件监听器:
Vue.component('my-title', {
props: ['level'],
render (h) {
return h(
`h${this.level}`,
{
on: {
click: this.clickHandler
}
},
this.$slots.default
)
},
methods: {
clickHandler (event) {
console.log('You clicked')
}
}
})
1.4 虚拟DOM
渲染函数的结果是使用
createElement
函数创建的节点树,在Vue中称为VNodes,它代表了组件在Vue维护的虚拟DOM中的视图。Vue不会直接用新的虚拟DOM树替换真实DOM树,因为这可能会导致大量昂贵的DOM操作。为了提高性能,Vue会比较两棵树的差异,只执行必要的DOM操作来更新真实DOM以匹配虚拟DOM。
1.5 JSX
JSX是一种在渲染函数的JavaScript代码中编写更像HTML的代码的语言,它实际上是JavaScript的类XML扩展。使用Babel可以将JSX代码转换为使用
h
函数的真实JavaScript代码。
例如,JSX代码:
export default {
props: ['message'],
render (h) {
return <p class="content">{this.message}</p>
}
}
会被转换为:
export default {
props: ['message'],
render (h) {
return h('p', { class: 'content' }, this.message)
}
}
由于
vue-cli
已经启用了JSX支持,我们可以在
.vue
文件中编写JSX代码。
1.6 博客内容结构(使用JSX)
创建一个新的
src/components/content
文件夹,并在其中创建
BlogContent.vue
文件。该组件代表右侧面板,负责显示相应的组件:
-
LocationInfo.vue
组件:如果在地图上选择了位置,可能会显示位置地址和名称。
- 下方根据不同情况显示:
-
NoContent.vue
组件:如果未选择位置,显示点击地图的提示。
-
CreatePost.vue
组件:如果有草稿帖子,显示表单。
-
PostContent.vue
组件:如果选择了真实帖子,显示内容和评论列表。
创建这些组件时,模板初始为空:
<template></template>
BlogContent.vue
组件的实现步骤如下:
1. 创建命名空间助手:
import { createNamespacedHelpers } from 'vuex'
const {
mapGetters: postsGetters,
mapActions: postsActions
} = createNamespacedHelpers('posts')
- 编写组件定义:
export default {
computed: {
...postsGetters([
'draft',
'currentPost'
]),
cssClass () {
return [
'blog-content',
{
'has-content': this.currentPost
}
]
}
}
}
- 编写渲染函数:
render (h) {
let Content
if (!this.currentPost) {
Content = NoContent
} else if (this.draft) {
Content = CreatePost
} else {
Content = PostContent
}
return <div class={this.cssClass}>
<LocationInfo />
<Content />
</div>
}
注意,在JSX中,标签首字母的大小写很重要。小写字母开头的标签会被视为
createElement
函数的字符串参数,解析为HTML元素或已注册组件;大写字母开头的标签会被视为变量。
重写
GeoBlog.vue
组件并添加
BlogContent
组件:
import AppMenu from './AppMenu.vue'
import BlogMap from './BlogMap.vue'
import BlogContent from './content/BlogContent.vue'
export default {
render (h) {
return <div class="geo-blog">
<AppMenu />
<div class="panes">
<BlogMap />
<BlogContent />
</div>
</div>
}
}
NoContent.vue
组件模板:
<template>
<div class="no-content">
<i class="material-icons">explore</i>
<div class="hint">Click on the map to add a post</div>
</div>
</template>
1.7 创建帖子
当用户在没有标记的地图位置上点击时,会创建一个草稿帖子,右侧面板的表单将编辑其内容。用户点击 “Create” 按钮时,将草稿发送到服务器,并将结果(新帖子数据)添加到帖子列表中。
1.7.1 草稿存储操作
在
posts
命名空间存储模块中,需要添加一些新的操作来创建、更新和清除草稿帖子:
actions: {
clearDraft ({ commit }) {
commit('draft', null)
},
createDraft ({ commit }) {
commit('draft', {
title: '',
content: '',
position: null,
placeId: null
})
},
setDraftLocation ({ dispatch, getters }, { position, placeId }) {
if (!getters.draft) {
dispatch('createDraft')
}
dispatch('updateDraft', {
position,
placeId
})
},
updateDraft ({ dispatch, commit, getters }, draft) {
commit('updateDraft', draft)
}
}
用户点击地图时调用
setDraftLocation
操作,会自动创建新草稿(如果没有)并更新其位置。
1.7.2 Blog Map组件更改
为了集成Vuex存储,需要对
BlogMap
组件进行一些更改:
1. 添加
posts
命名空间模块的Vuex助手,并对
maps
模块的助手进行重命名:
const {
mapGetters: mapsGetters,
mapActions: mapsActions
} = createNamespacedHelpers('maps')
const {
mapGetters: postsGetters,
mapActions: postsActions
} = createNamespacedHelpers('posts')
-
添加
draftgetter:
computed: {
...mapsGetters([
'center',
'zoom'
]),
...postsGetters([
'draft'
])
}
-
添加
setDraftLocation操作:
methods: {
...mapsActions([
'setCenter',
'setUserPosition',
'setZoom'
]),
...postsActions([
'setDraftLocation'
])
}
1.7.3 点击处理
为地图添加点击处理程序:
1. 在地图组件中添加点击事件:
<googlemaps-map
:center="center"
:zoom="zoom"
:options="mapOptions"
@update:center="setCenter"
@update:zoom="setZoom"
@click="onMapClick"
>
- 添加相应的方法:
onMapClick (event) {
this.setDraftLocation({
position: event.latLng,
placeId: event.placeId
})
}
1.7.4 幽灵标记
在草稿位置显示透明标记:
<googlemaps-marker
v-if="draft"
:clickable="false"
:label="{
color: 'white',
fontFamily: 'Material Icons',
text: 'add_circle'
}"
:opacity=".75"
:position="draft.position"
:z-index="6"
/>
1.8 帖子表单
CreatePost.vue
组件用于显示输入新帖子详细信息(如标题和内容)的表单。
- 创建模板:
<template>
<form
class="create-post"
@submit.prevent="handleSubmit">
<input
name="title"
v-model="title"
placeholder="Title"
required />
<textarea
name="content"
v-model="content"
placeholder="Content"
required />
<div class="actions">
<button
type="button"
class="secondary"
@click="clearDraft">
<i class="material-icons">delete</i>
Discard
</button>
<button
type="submit"
:disabled="!formValid">
<i class="material-icons">save</i>
Post
</button>
</div>
</form>
</template>
-
映射
posts模块的Vuex助手:
import { createNamespacedHelpers } from 'vuex'
const {
mapGetters: postsGetters,
mapActions: postsActions
} = createNamespacedHelpers('posts')
- 添加必要的getters和方法:
export default {
computed: {
...postsGetters([
'draft'
])
},
methods: {
...postsActions([
'clearDraft',
'createPost',
'updateDraft'
])
}
}
- 添加与表单输入元素绑定的计算属性:
title: {
get () {
return this.draft.title
},
set (value) {
this.updateDraft({
...this.draft,
title: value
})
}
},
content: {
get () {
return this.draft.content
},
set (value) {
this.updateDraft({
...this.draft,
content: value
})
}
},
formValid () {
return this.title && this.content
}
-
添加
handleSubmit方法:
handleSubmit () {
if (this.formValid) {
this.createPost(this.draft)
}
}
1.9 发送请求
1.9.1 创建
createPost
操作
在
posts
Vuex模块中创建
createPost
操作:
async createPost ({ commit, dispatch }, draft) {
const data = {
...draft,
position: draft.position.toJSON()
}
const result = await $fetch('posts/new', {
method: 'POST',
body: JSON.stringify(data)
})
dispatch('clearDraft')
commit('addPost', result)
dispatch('selectPost', result._id)
}
该操作准备数据,发送POST请求到服务器,清除草稿,将新帖子添加到存储并选择该帖子。
1.9.2 创建
selectPost
操作
async selectPost ({ commit }, id) {
commit('selectedPostId', id)
// TODO fetch the post details (comments, etc.)
}
1.10 获取帖子
1.10.1 存储操作
每次用户平移或缩放地图导致地图边界改变时,需要获取帖子。为了解决请求顺序问题,使用唯一标识符来中止先前的请求。
-
在
posts.js文件顶部声明唯一标识符:
let fetchPostsUid = 0
-
添加
fetchPosts操作:
async fetchPosts ({ commit, state }, { mapBounds, force }) {
let oldBounds = state.mapBounds
if (force || !oldBounds || !oldBounds.equals(mapBounds)) {
const requestId = ++fetchPostsUid
const ne = mapBounds.getNorthEast()
const sw = mapBounds.getSouthWest()
const query = `posts?ne=${encodeURIComponent(ne.toUrlValue())}&sw=${encodeURIComponent(sw.toUrlValue())}`
const posts = await $fetch(query)
if (requestId === fetchPostsUid) {
commit('posts', {
posts,
mapBounds
})
}
}
}
1.10.2 操作调度
在
maps
存储中创建
setBounds
操作,当地图平移或缩放后空闲时调度该操作,它会调度
posts
模块的
fetchPosts
操作:
setBounds ({ dispatch }, value) {
dispatch('posts/fetchPosts', {
mapBounds: value
}, {
root: true
})
}
总结
通过以上步骤,我们完成了地理定位博客的主要功能开发,包括渲染函数、JSX的使用、虚拟DOM的处理、帖子的创建和获取等。整个开发过程涉及到Vuex存储、组件通信、地图交互等多个方面,希望这些内容能帮助你更好地理解和开发类似的应用。
流程图
graph TD;
A[用户点击地图] --> B{是否有草稿};
B -- 否 --> C[创建草稿];
B -- 是 --> D[更新草稿位置];
C --> D;
D --> E[显示CreatePost组件];
E --> F{表单是否有效};
F -- 是 --> G[发送请求创建帖子];
G --> H[清除草稿];
H --> I[添加新帖子到存储];
I --> J[选择新帖子];
F -- 否 --> K[提示表单无效];
表格
| 操作 | 描述 |
|---|---|
clearDraft
| 清除草稿帖子 |
createDraft
| 创建草稿帖子 |
setDraftLocation
| 设置草稿帖子的位置 |
updateDraft
| 更新草稿帖子的内容 |
createPost
| 发送请求创建新帖子 |
selectPost
| 选择帖子 |
fetchPosts
| 获取地图边界内的帖子 |
setBounds
|
调度
fetchPosts
操作
|
2. 功能总结与拓展思路
2.1 已实现功能回顾
我们已经完成了地理定位博客的多个核心功能,下面对这些功能进行简要回顾:
-
渲染函数与JSX
:掌握了
createElement
函数(别名
h
)的使用,通过渲染函数实现动态模板,同时借助JSX让代码更接近HTML语法,提高了开发效率和代码可读性。
-
虚拟DOM
:了解了Vue通过虚拟DOM和差异比较来优化DOM操作,减少性能开销的原理。
-
帖子管理
:实现了草稿帖子的创建、更新和清除,以及真实帖子的创建、选择和获取功能,涉及到Vuex存储模块的多个操作。
-
地图交互
:实现了地图点击创建帖子、显示幽灵标记等交互功能,增强了用户体验。
2.2 拓展思路
基于已实现的功能,我们可以进一步拓展地理定位博客的功能,以下是一些拓展思路:
-
帖子详情页
:目前
selectPost
操作中只是简单设置了选中帖子的ID,后续可以实现帖子详情页,展示帖子的详细信息,如评论列表、点赞数等。
-
用户认证
:添加用户认证功能,让用户可以注册、登录,实现用户专属的帖子管理和互动功能。
-
地图标记优化
:可以对地图上的标记进行优化,如根据帖子的热度、点赞数等显示不同颜色或大小的标记。
-
搜索功能
:添加搜索功能,让用户可以根据关键词搜索帖子,提高信息查找的效率。
2.3 代码优化建议
在开发过程中,我们可以对代码进行一些优化,提高代码的可维护性和性能:
-
代码复用
:提取一些重复的代码逻辑,封装成组件或函数,提高代码的复用性。
-
错误处理
:在发送请求时,添加错误处理机制,提高系统的健壮性。例如,在
createPost
操作中,可以捕获请求异常并提示用户。
async createPost ({ commit, dispatch }, draft) {
try {
const data = {
...draft,
position: draft.position.toJSON()
}
const result = await $fetch('posts/new', {
method: 'POST',
body: JSON.stringify(data)
})
dispatch('clearDraft')
commit('addPost', result)
dispatch('selectPost', result._id)
} catch (error) {
console.error('创建帖子失败:', error)
// 可以在这里添加提示用户的逻辑
}
}
- 性能优化 :在获取帖子时,可以考虑添加缓存机制,减少不必要的请求。
2.4 总结与展望
通过本文的介绍,我们详细了解了地理定位博客的开发过程,从渲染函数、JSX的使用到帖子的创建和获取,再到地图交互功能的实现。整个开发过程涉及到Vue、Vuex、地图API等多个技术点,希望这些内容能帮助你更好地理解和开发类似的应用。
未来,我们可以根据拓展思路进一步完善地理定位博客的功能,为用户提供更好的体验。同时,不断优化代码,提高系统的性能和可维护性。
流程图
graph TD;
A[用户进入博客] --> B{是否登录};
B -- 否 --> C[显示登录/注册页面];
C --> D[用户登录/注册];
D --> E[显示博客主页];
B -- 是 --> E;
E --> F{是否点击地图};
F -- 是 --> G[创建/编辑帖子];
F -- 否 --> H[浏览帖子列表];
G --> I{是否提交帖子};
I -- 是 --> J[发送请求创建帖子];
J --> K[更新帖子列表];
I -- 否 --> G;
H --> L{是否点击帖子};
L -- 是 --> M[显示帖子详情页];
L -- 否 --> H;
表格
| 拓展功能 | 描述 |
|---|---|
| 帖子详情页 | 展示帖子的详细信息,如评论列表、点赞数等 |
| 用户认证 | 实现用户注册、登录功能,管理用户专属帖子 |
| 地图标记优化 | 根据帖子热度、点赞数等显示不同标记 |
| 搜索功能 | 让用户可以根据关键词搜索帖子 |
超级会员免费看
667

被折叠的 条评论
为什么被折叠?



