如何写代码
我们先思考一个问题,假设我们拿到一个PRD的时候,我们应该先去思考什么?是UI/交互?就开始立马的划分组件?我个人觉得,我们应该先思考数据?数据来源,数据结构,数据传递,只有先考虑了数据,后面所有的设计才会是一个合理的结果
我们来思考一句话,程序等于什么?程序 = 数据结构 + 算法
再来思考一句话,什么才叫好代码?我觉得,代码通俗易懂它就是好代码
怎么设计代码(本人风格)
从大的结构来说 接口数据 => service / controller => 视图(view 组件层)
为什么要这样设计,假设后端给你的数据结构并不是你想要的,这时候你怎么办?是让他改还是你直接硬着写还是自己拼接处理数据?如果是硬着头皮鞋,之后导致数据混乱,视图层代码结构混乱。我个人倾向将数据的处理拆分出去,视图只管拿到数据就渲染,不做过多的操作。
组件设计
很多的交互归根结底都是对数据的操作,我就说说我一般会怎么去设计
我自己是倾向对数据可以做一些稍微的处理以达到可以方便自己的开发的地步
例如:
// 接口返回的数据
const res = [{ ... }, { ... }, { ... }]
// 假设前端需要某些自定义字段或者需要改变数据结构
const frontEndList = [{ type: 'input', ... }, { type: 'label', ... }]
// 然后前端根据状态去查找对应的数据去操作即可
组件具体数据传递设计
在组件开发中,我们会根据具体的框架模型去开发,例如vue的 data,methods, computed,,, 这样的固定形式去开发,其实这样是不好的,随便可能是一个需求很快就能开发完成(一次性项目当我没说),但后期维护成本比极高,属于自己写的爽,接手的人很难受,这时候就会出现个死循环,,,(就大家一起难受)
那么我想说的是
派发器 => 模式 => 改造 => 组件逻辑部分
method => data
method
Type => 事件 => 逻辑 => type => 派发器 => 数据更改
合理的组件设计
页面组件 -- 快级组件容器(无逻辑部分)view / test.vue
快级组件 -- 组件出口 + 子组件 -- 数据保存 + 逻辑
子组件 -- 视图 = 数据 => 属性
方法 => 事件传递 => 子组件
案例
文件目录
actions => counter.js
const PLUS = 'PLUS'
const MINUS = 'MINUS'
export {
PLUS,
MINUS
}
Components => Counter
Button.vue
<template>
<button :action="action" @click="compute">{{ innerText }}</button>
</template>
<script>
export default {
name: "Button",
props: {
innerText: String,
action: String
},
methods: {
compute() {
this.$emit('dispatch', this.action)
}
}
};
</script>
<style scoped>
</style>
Result.vue
<template>
<div>
<h1>{{ result }}</h1>
</div>
</template>
<script>
export default {
name: "Result",
props: {
result: Number
}
};
</script>
<style scoped>
</style>
Index.vue
<template>
<div>
<counter-result :result="result" />
<counter-button
innerText="+"
action="PLUS"
@dispatch="dispatch"
/>
<counter-button
innerText="-"
action="MINUS"
@dispatch="dispatch"
/>
</div>
</template>
<script>
import CounterResult from './Result'
import CounterButton from './Button'
import dispatch from '../../dispatchers/counter'
export default {
name: "index",
components: {
CounterResult,
CounterButton
},
data() {
return {
result: 0
}
},
methods: {
dispatch(...arg) {
dispatch(this)(...arg)
}
}
};
</script>
<style scoped>
</style>
Dispatchers => counter.js
import counterReducer from "../reducers/counter"
import { PLUS, MINUS} from "../actions/counter";
export default (ctx) => {
const { plus, minus } = counterReducer(ctx.$data)
return function(type, ...arg) {
switch (type) {
case PLUS:
ctx.result = plus(...arg)
break
case MINUS:
ctx.result = minus(...arg)
break
default:
break
}
}
}
Reducers => counter.js
function counterReducer(data) {
function plus() {
return data.result + 1
}
function minus() {
return data.result - 1
}
return {
plus,
minus
}
}
export default counterReducer
Views => Counter => index
<template>
<div>
<counter />
</div>
</template>
<script>
import counter from '../../components/Counter'
export default {
name: "index",
components: {
counter
}
};
</script>
<style scoped>
</style>
在App.vue 导入即可
其实它就是在仿造 vuex redue 这类的数据仓库的操作
当然其实也可以深入思考怎么去使用vuex(后期有机会可以更新一次,其实就是上面的操作)
关于低代码平台/无代码平台
其实大部分场景就是JSON => 视图 就好像 dom节点 => JSON对象,style,click 等属性
我们以最简单的后台管理系统最常见的表格为例子 VUE(直接上代码)
项目描述:在后台管理系统中,有非常多的表格类型的UI,如果每一个表格都去复制会十分难受,代码会比较臃肿,聪明的同学可以说开发一个可配置表格即可,但依然要去写非常多个配置,其实我们可以只需要写一个即可,(需要后端配合)在这个view 中,我们的路由会这样设计 /x x x/xxx/user-list ,其实这个就是一个接口地址,在可配置表格当中我们拿到这个动态参数 将其转换成接口地址 user/list 对应的就是拿到用户表的数据,user/list/del/123 删除 user/list/edit 修改, 这样的好处就是他可以很好的结合权限,已经有一个我们需要新增一个例如权限表,这时候后台管理系统直接操作即可,前端代码是不需要发版的(需要后端配合,例如表格的dom接口需要从接口取, 如果当前业务不支持这样业务实现的,前端可以将配置写在本地代码当中,接口可以写在路由的meta当中)
文件划分
Router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/tableListTest/:list_id',
component: () => import('../views/tableListTest')
}
]
const router = new VueRouter({
routes
})
router.beforeEach((to,from,next) => {
if(to.path === '/') {
next({ path: '/tableListTest/test_¬1'})
}
next()
})
export default router
component/tableList.vue
<template>
<div>
<el-card class="tableList">
<div v-if="tableListConfig.select && tableListConfig.select.show">
<el-input :placeholder="tableListConfig.select.title" v-model="tableValue">
<el-button slot="append" :icon="`${tableListConfig.select.icon ? tableListConfig.select.icon : 'el-icon-search'}`" @click="onSearch"></el-button>
</el-input>
</div>
<div>
<el-table
:data="tableListData"
stripe
border
>
<el-table-column
v-for="(item) in tableListConfig.tableKeyList"
:fixed="item.fixed"
:prop="item.key"
:label="item.title"
:align="item.align"
:width="item.width"
:key="item.key"
>
<template slot-scope="scope" >
<template v-if="item.type === 'label'">
<span>{{ scope.row[item.key] }}</span>
</template>
<template v-else-if="item.type === 'btnList'">
<el-button v-for="(btn, index) in item[item.key]" @click="handleClickOperation(btn)" type="primary" :key="index">
{{ btn.text }}
</el-button>
</template>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</div>
</template>
<script>
import tableDialog from './tableDialog'
import { httpGetTestList, TABLE_EVENT } from '../service/test'
export default {
name: 'tableList',
components: {
tableDialog
},
data () {
return {
tableValue: '',
dialogConfig: {},
tableListConfig: {},
tableListData: [],
open: false
}
},
props: {
},
watch: {
'$route'(newV) {
this.getList(newV.params.list_id)
}
},
async created () {
await this.getList(this.$route.params.list_id)
},
methods: {
/**
* 请球员接口
* @param path 接口地址
*/
async getList (path) {
// 假设请求回来的数据
const res = await httpGetTestList(path)
// 将配置赋值给表格
const { conf, data } = res
this.tableListConfig = conf
this.tableListData = data.data
},
handleClickOperation(item) {
switch (item.type) {
case 'add': this.onAdd(item);break;
case 'del': this.onDel(item);break;
case 'edit': this.onEdit(item);break;
}
},
// 搜索
onSearch() {
// TODO
},
onAdd(item) {
// TODO
},
onEdit(item) {
// TODO
},
async onDel(item) {
const res = await TABLE_EVENT[item.event_type](item)
if (res.code === 1) {
this.$message.success(res.msg)
}
},
closeDialog() {
}
}
}
</script>
<style scoped>
.tableList {
width: 70%;
position: relative;
margin: 0 auto;
}
.operate div{
display: inline;
}
</style>
conf/table.js
export const LIST_DEL_BY_KEY = 'LIST_DEL_BY_KEY'
export const LIST_ADD = 'LIST_ADD'
export const LIST_EDIT = 'LIST_EDIT'
contorller/tables/test-1.js
import { LIST_DEL_BY_KEY, LIST_EDIT, LIST_ADD } from '../../conf/table.js'
export default {
select: {
show: true,
// 搜索框标题
title: "请输入姓名",
// 使用的图标 undefined则显示默认图标
icon: undefined,
},
tableKeyList: [
{
key: 'id',
title: 'id',
width: 80,
align: "center",
fixed: true,
type: 'label' // 新增,编辑 等操作需要用到
},
{
key: 'name',
title: '姓名',
align: "center",
width: 150,
fixed: false,
type: 'label' // 新增,编辑 等操作需要用到
},
{
key: 'sex',
title: '性别',
align: "center",
width: 150,
fixed: false,
type: 'label' // 新增,编辑 等操作需要用到
},
{
key: 'age',
title: '年龄',
align: "center",
width: 150,
fixed: false,
type: 'label' // 新增,编辑 等操作需要用到
},
{
key: 'position',
title: '职业',
align: "center",
width: 150,
fixed: false,
type: 'label' // 新增,编辑 等操作需要用到
},
{
key: 'btnList',
title: '操作',
align: "center",
width: 280,
fixed: "right",
type: 'btnList',
btnList: [
{
type: 'add',
event_type: LIST_ADD,
text: '新增',
icon: '',
permission: 'admin' // 结合vuex / 自定义权限指令
},
{
type: 'del',
event_type: LIST_DEL_BY_KEY,
key: 'id',
text: '删除',
icon: '',
permission: 'admin' // 结合vuex / 自定义权限指令
},
{
type: 'edit',
event_type: LIST_EDIT,
text: '编辑',
icon: '',
permission: 'admin' // 结合vuex / 自定义权限指令
}
]
}
]
}
// 复制三个 test-2 test-3 test-4
contorller/table.js
const moduleFiles = require.context('./tables', true, /.js$/)
const modules = moduleFiles.keys().reduce((modules, path) => {
const moduleName = path.replace(/^./(.*).\w+$/, '$1')
const value = moduleFiles(path)
module[moduleName] = value.default
return module
}, {})
export default modules
core/asyncCompontents.js
import Vue from 'vue'
import {
Table,
Card,
Input,
Icon,
Button,
TableColumn,
MessageBox,
Message,
Dialog,
Form,
FormItem,
Tooltip
} from 'element-ui'
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Card)
Vue.use(Input)
Vue.use(Icon)
Vue.use(Button)
Vue.use(Dialog)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Tooltip)
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$message = Message
resData/test-1.js
export default {
data: [
{
id: 0,
name: 'lee',
sex: '男',
age: '21',
position: '前端'
},
{
id: 1,
name: '张三',
sex: '男',
age: '18',
position: '后端'
},
{
id: 2,
name: '李四',
sex: '女',
age: '23',
position: '前端'
},
{
id: 3,
name: '王五',
sex: '男',
age: '24',
position: '产品经理'
}
]
}
// 复制三个 test-2 test-3 test-4 随便更改几个数据
service/test.js
import tableKeyConfig from '../controller/table'
import { LIST_EDIT, LIST_ADD,LIST_DEL_BY_KEY } from '../conf/table'
/**
* 列表请求数据
* @param apiPath
* @param params
*/
export const httpGetTestList = (apiPath, params) => {
const path = apiPath.replace('_', '-') + '.js'
const confName = apiPath.replace('_', '-')
return import('../resData/' + path).then((res) => {
// 讲表格数据和配置返回给前端
const data = {
conf: tableKeyConfig[confName],
data: res.default
}
return Promise.resolve(data)
})
}
export const TABLE_EVENT = {
[LIST_DEL_BY_KEY]: (row) => {
return Promise.resolve({
code: 1,
msg: '删除成功'
})
},
[LIST_ADD]: (row) => {
return Promise.resolve({
code: 1,
msg: '新增成功'
})
},
[LIST_EDIT]: (row) => {
return Promise.resolve({
code: 1,
msg: '编辑成功'
})
}
}
views/tableListTest.vue
<template>
<div class="main">
<div style="width: 100%; height: 100px">
<el-button type="primary" @click="goTableList('test_1')">表格1</el-button>
<el-button type="primary" @click="goTableList('test_2')">表格2</el-button>
<el-button type="primary" @click="goTableList('test_3')">表格3</el-button>
<el-button type="primary" @click="goTableList('test_4')">表格4</el-button>
</div>
<table-list ref="tableListRef"/>
</div>
</template>
<script>
import tableList from '../components/tableList'
export default {
name: 'tableListTest',
components: {
tableList
},
data () {
return {
}
},
methods: {
goTableList(params) {
this.$router.push(`/tableListTest/${params}`)
}
}
}
</script>
<style scoped>
.main {
display: flex;
justify-content: space-between;
width: 100%;
height: 100%;
}
.drag {
width: 20%;
height: 100%;
background-color: orange;
}
.render {
width: 50%;
height: 100%;
background-color: aqua;
}
.edit {
width: 30%;
height: 100%;
background-color: bisque;
}
</style>
App.vue
<template>
<div id="app">
<router-view></router-view>
</div>
</template>
<script>
</script>
<style lang="less">
* {
padding: 0;
margin: 0;
}
html, body, #app {
width: 100%;
height: 100%;
}
</style>
Main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './core/asyncComponents'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
希望和大家一起来探讨不足点,欢迎指教