摘要:其實這里還是有漏洞的,坐等高手指出來微笑臉后臺沒有用生成一個完整的架構(gòu)。來自不同視圖的行為需要變更同一狀態(tài)。
最近對vue很感興趣,趁閑暇時間,模仿了wunderlist里面的部分功能,編寫了前后端分離的基于vue技術(shù)棧和express的todolist小項目。寫這篇博文來總結(jié)思考下。項目所在github,可以自行參考克隆。
本人博客
總體概覽整個項目最終做成的樣子如下:
大家都看到了,整體實現(xiàn)的功能還是比較簡單的。由于對express也很感興趣,就干脆自己動手做了全棧。另外說一下:這個項目只是自己摸索vue與express過程中,做出的成果,如果有哪個地方不對的,還請大神多多指教。
整個todolist界面分為左側(cè)的目錄分類和右側(cè)的list。用戶切換不同的目錄可以對應(yīng)到相應(yīng)的list任務(wù)中,并且在該任務(wù)中能夠添加list和刪除list,也能夠標記已完成與未完成。這些看上去都很簡單,但是里面存在了挺多小細節(jié)的,我認為,作為一個新手,尤其是對vue新,對express新又對他們很感興趣的新手,拿這個項目來練手個人覺得很合適。
不多說廢話了,先來看看我的項目目錄與大致的介紹吧。
項目結(jié)構(gòu)├── README.md //這里是readme說明文檔 ├── node_modules //一些依賴在這里 ├── build │ ├── build.js │ └── dev.js ├── db //數(shù)據(jù)庫相關(guān)的東西放在這里 │ └── dbConfig.js //數(shù)據(jù)庫配置文件 ├── dist //webpack編譯后的目標文件夾 ├── package.json //這個就不說了,是個前端都懂 ├── server //服務(wù)器端相關(guān)的文件都在這里 │ └── app.js //server服務(wù),后臺服務(wù)入口文件 ├── src //前端源文件都在這里 │ ├── App.vue //vue頂級組件,包含了vue-router │ ├── components //各個子組件 │ │ ├── common //包含了頁面的公共模塊,比如header,footer等 │ │ ├── menu-item.vue //左側(cè)菜單欄組件 │ │ ├── search.vue //搜索框組件 │ │ └── todo-list.vue //list組件 │ ├── config //一些前端配置的東西可以放在這里 │ ├── directives //vue的一些指令封裝可以放在這里 │ ├── filters //vue的一些過濾器可以放在這里 │ ├── images //放置圖片 │ │ ├── Shapes.jpg │ │ ├── article.jpeg │ │ └── avatar.png │ ├── less //公共樣式less相關(guān)放在這里 │ │ ├── common.less │ │ ├── fonts │ │ ├── index.less │ │ ├── lessfont │ │ ├── mixin.less │ │ ├── reset.less │ │ └── variable.less │ ├── main.js //前端入口文件 │ ├── pages //放置不同的頁面,本項目只有一個頁面,所以暫定只有一個 │ │ └── index.vue │ ├── route.js //路由配置文件 │ └── store //vuex相關(guān)邏輯放在這里 │ ├── actions.js //actions相關(guān) │ ├── getters.js //getter相關(guān) │ ├── index.js //頂端vuex設(shè)置,入口文件 │ ├── modules //放置模塊 │ ├── mutations.js //mutation相關(guān) │ ├── plugins.js //插件相關(guān) │ └── state.js //state相關(guān) ├── webpack.config.base.js //webpack基本配置 ├── webpack.config.dev.js //開發(fā)環(huán)境配置 └── webpack.config.prod.js //線上環(huán)境配置,但沒有測試過也沒有怎么研究,可以暫時忽略
下面來分前后端兩個模塊大致講解一下:
后端首先要準備mysql數(shù)據(jù)庫服務(wù),我用MYSQLWorkbench客戶端界面新建數(shù)據(jù)庫,初始化庫表信息,當然你也可以不用圖形界面,直接用命令行。在dbConfig.js中配置好數(shù)據(jù)庫設(shè)置
module.exports = { host : "localhost", user : "your user name default root", password : "your password", database : "taskiller" }
本項目中,數(shù)據(jù)庫數(shù)據(jù)表的新建,并沒有寫在server服務(wù)中,在實際的項目中應(yīng)該有個自動化的腳本自動創(chuàng)建你需要的數(shù)據(jù)表和需要的字段信息。因為是當成練手項目做的,所以一切都從簡了,把項目用到的數(shù)據(jù)庫和數(shù)據(jù)表都事先建立好了。我的數(shù)據(jù)庫名是taskiller,需要在這個數(shù)據(jù)庫中,建兩個表:todo_list和menu,todolist用來存儲list信息,menu用來存儲目錄信息。示例:
menu表
+----+--------+----------+ | id | name | selected | +----+--------+----------+ | 1 | sdd | 0 | +----+--------+----------+
todo_list表
+--------------+----+------+---------+ | text | id | done | menu_id | +--------------+----+------+---------+ | sdfs | 46 | 0 | 1 | | this is life | 47 | 0 | 4 | +--------------+----+------+---------+
接下來就是后端服務(wù)文件app.js了,看代碼說話吧:
const path = require("path") const express = require("express") const mysql = require("mysql"); const dbConfig = require("../db/dbConfig"); const bodyParser = require("body-parser") const insertMenu = "INSERT INTO menu SET ?" const getMenu = "SELECT * FROM menu WHERE id = ?" const getAllMenu = "SELECT * FROM menu" const getTodolist = "SELECT * FROM todo_list WHERE menu_id = ?" const insertTodolist = "INSERT INTO todo_list SET ?" const deleteTodo = "DELETE from todo_list WHERE id = ?" const updateTodolist = "UPDATE todo_list SET done = ? WHERE id = ?" let menus; //所有menu列表緩存 const app = express(); app.use(bodyParser()); //連接數(shù)據(jù)庫 let connection = mysql.createConnection(dbConfig); app.all("*", function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By"," 3.2.1") res.header("Content-Type", "application/json;charset=utf-8"); next(); }); //添加目錄 app.post("/menu/add", function (req, res, next) { let reqParam = req.body; connection.query(insertMenu, reqParam, function(error, results, fields) { if(error) throw error; console.log(results, fields) }) res.sendStatus(200); next() }); //得到所有目錄 app.get("/menu/get", function(req, res, next) { connection.query(getAllMenu, function(error, results, fields) { if(error) throw error; menus = results; res.json(results); next() }) }) //得到指定id的目錄 app.get("/menu/get/:id", function(req, res, next) { console.log("ID:", req.params.id); connection.query(getMenu, req.params.id, function(error, results, fields) { if(error) throw error; res.json(results); }) }) //根據(jù)目錄獲取todolist app.get("/todolist/get/:id", function(req, res, next) { connection.query(getTodolist, req.params.id, function(error, results, fields) { if(error) return error; res.json(results); }) }) //添加todolist到數(shù)據(jù)庫中 app.post("/todolist/add", function(req, res, next) { //text,done, menu_id let reqParam = { "text": req.body.data.text, "done": false, "menu_id": req.body.data.curMenu }; var insertId; connection.query(insertTodolist, reqParam, function(error, results, fields) { if(error) throw error; insertId = results.insertId; reqParam.id = insertId; res.json(reqParam) }) }) //刪除todolist app.post("/todolist/delete", function(req, res, next) { let reqParam = req.body.id connection.query(deleteTodo, reqParam, function(error, results, fields) { if(error) throw error; console.log(results, fields) }) res.sendStatus(200); }) //改變todolist狀態(tài) app.post("/todolist/toggle", function(req, res, next) { let reqParam = req.body; console.log(reqParam) connection.query(updateTodolist, [!reqParam.done, reqParam.id], function(error, results, fields) { if(error) throw error; console.log(results) res.sendStatus(200); }) }) app.listen(3001, function() { console.log("listening on port 3001") }) connection.connect(function(err) { if (err) { console.error("error connecting: " + err.stack); return; } console.log("connected as id " + connection.threadId); });
很簡單,所有的接口功能應(yīng)該都是一目了然。因為筆者主要鍛煉的還是vue相關(guān)的,express只是有興趣略微帶過,因此沒有考慮很復雜很完善的一些邏輯。有幾個注意事項:
因為前端的地址端口是3000,后端server的端口又設(shè)定了3001,這就涉及到了跨域,因此我加了這段代碼,來解決跨域的問題。
app.all("*", function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("X-Powered-By"," 3.2.1") res.header("Content-Type", "application/json;charset=utf-8"); next(); });
其實這里還是有漏洞的,坐等高手指出來(微笑臉)
后臺express沒有用express-generator生成一個完整的架構(gòu)。筆者之前嘗試過用這種一鍵生成的工具快速搭建后臺環(huán)境,但是這樣就都用現(xiàn)成的,好多東西概念就會非常模糊,不太好掌握一些技術(shù)細節(jié),也不會很透徹理解這樣寫的架構(gòu)到底是為什么,為什么不采用其他架構(gòu)方式?所以,筆者決定自己純手寫后臺的server,這個最初的版本是寫的最簡單的版本,等后期再深入研究express的時候,再把這個雛形向著可擴展性和模塊化發(fā)展。不過已經(jīng)對express很熟的同學完全可以不照著我這個小白的寫法寫~
前端 webpack配置前端剛開始我遇到的門檻就是webpack一些配置,網(wǎng)上的教程真的是五花八門,由于本人的目的是學習vue的,并不是搗鼓webpack,所以,在webpack配置方面并沒有花很多的時間研究,也是在網(wǎng)上找教程,慢慢摸索倒騰出來的。不過經(jīng)過這次倒騰我認識到,我有必要研究下webpack的一些東西了。不然配置個東西,太痛苦,并且前端技術(shù)日新月異,網(wǎng)上的教程五花八門,有老舊版本的,有新的版本的,很容易讓人摸不著頭腦,建議還是去官網(wǎng)學習比較好。后期我研究了再寫博文總結(jié)下經(jīng)驗。
這里先貼一下我的webpack配置,有些地方做了簡要的注釋。
webpack.config.base.js
const webpack = require("webpack"); const path = require("path"); const fs = require("fs"); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const autoprefixer = require("autoprefixer"); const PATHS = { src: path.resolve(__dirname, "./src"), dist: path.resolve(__dirname, "./dist") } module.exports = { entry: { app: "./src/main.js", // 整個SPA的入口文件, 一切的文件依賴關(guān)系從它開始 vendors: ["vue", "vue-router"] // 需要進行多帶帶打包的文件 }, output: { path: PATHS.dist, filename: "js/[name].js", publicPath: "/dist/", // 部署文件 相對于根路由 chunkFilename: "js/[name].js" // chunk文件輸出的文件名稱 具體格式見webpack文檔, 注意區(qū)分 hash/chunkhash/contenthash 等內(nèi)容, 以及存在的潛在的坑 }, devtool: "#eval-source-map", // 開始source-map. 具體的不同配置信息見webpack文檔 module: { rules: [{ test: /.vue$/, loader: "vue-loader" }, { test: /.js/, loader: "babel-loader", exclude: /node_modules/ }, { test: /.(png|jpe?g|gif|svg)(?.*)?$/, loader: "url-loader?limit=10240&name=images/[name].[ext]" }, { test: /.less/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [ "css-loader", "less-loader" ] }) }, { test: /.css/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: [ "css-loader" ] }) }, { test: /.(eot|svg|ttf|woff|woff2|png)w*/, loader: "file-loader" } ] }, resolve: { alias: { "vue$": "vue/dist/vue.common.js", "components": path.join(__dirname, "src/components"), // 定義文件路徑, 加速打包過程中webpack路徑查找過程 "lib": path.join(__dirname, "src/lib"), "less": path.join(__dirname, "src/less"), "filters": path.join(__dirname, "src/filters"), "directives": path.join(__dirname, "src/directives"), }, extensions: [".js", ".less", ".vue", "*", ".json"] // 可以不加后綴, 直接使用 import xx from "xx" 的語法 }, plugins: [ new HtmlWebpackPlugin({ // html模板輸出插件 title: "task kill", template: `${PATHS.dist}/template/index.ejs`, inject: "body", filename: `${PATHS.dist}/pages/index.html` }), new ExtractTextPlugin({ // css抽離插件,多帶帶放到一個style文件當中. filename: `css/style.css`, allChunks: true, disable: false }), // 將vue等框架/庫進行多帶帶打包, 并輸入到vendors.js文件當中 // 這個地方commonChunkPlugin一共會輸出2個文件, 第二個文件是webpack的runtime文件 // runtime文件用以定義一些webpack提供的全局函數(shù)及需要異步加載的chunk文件 // 具體的內(nèi)容可以看我寫的blog // [webpack分包及異步加載套路](https://segmentfault.com/a/1190000007962830) // [webpack2異步加載套路](https://segmentfault.com/a/1190000008279471) new webpack.optimize.CommonsChunkPlugin({ names: ["vendors", "manifest"] }) ] }
webpack.config.dev.js
const merge = require("webpack-merge"); module.exports = merge(require("./webpack.config.base"), { devServer: { proxy: { "/api": { target: "http://localhost:3001", changeOrigin: true, secure: false, pathRewrite: { "^/api": "" } } } } })
webpack.config.prd.js就不貼了,這次我也沒有用上,大家有興趣可以直接去github里看。
邏輯設(shè)計 vuex概要在這里不得不感謝vuex,這個東西對開發(fā)效率的提升真的很有幫助,vuex不熟悉的童鞋可以去官網(wǎng)查閱,建議既然學習了vue了,vuex必不可少,真的會節(jié)省你很多的開發(fā)時間。這里我就做個簡要的介紹:
vuex是一個專為vue開發(fā)的狀態(tài)管理模式,它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預測的方式變化。
這個狀態(tài)自管理應(yīng)用包含以下幾個部分:
state,驅(qū)動應(yīng)用的數(shù)據(jù)源;
view,以聲明方式將state映射到視圖;
actions,響應(yīng)在view上的用戶輸入導致的狀態(tài)變化。
比如以下的單一數(shù)據(jù)流示意圖:
但是,有過組件編寫經(jīng)驗的童鞋應(yīng)該知道,當我們的應(yīng)用遇到多個組件共享狀態(tài)時,單向數(shù)據(jù)流的簡潔性很容易被破壞:
多個視圖依賴于同一狀態(tài)。
來自不同視圖的行為需要變更同一狀態(tài)。
對于問題一,傳參的方法對于多層嵌套的組件將會非常繁瑣,并且對于兄弟組件間的狀態(tài)傳遞無能為力。對于問題二,我們經(jīng)常會采用父子組件直接引用或者通過事件來變更和同步狀態(tài)的多份拷貝。以上的這些模式非常脆弱,通常會導致無法維護的代碼。
因此,誕生了vuex,用來把組件的共享狀態(tài)抽取出來,以一個全局單例模式管理。在這種模式下,組件樹構(gòu)成了一個巨大的“視圖”,不管在樹的哪個位置,任何組件都能獲取狀態(tài)或者觸發(fā)行為!
另外,通過定義和隔離狀態(tài)管理中的各種概念并強制遵守一定的規(guī)則,代碼也會變得更結(jié)構(gòu)化且易維護。
來看看這張經(jīng)典的圖例:
大致的就介紹到這里啦,需要更加深入的童鞋可以移步官網(wǎng)。下面針對本項目的邏輯,介紹下我設(shè)計的vuex。
state設(shè)計由于目前實現(xiàn)的邏輯還是較為簡單,因此,只有涉及了3個state:
export const state = { todos: [], //當前選中目錄curMenu對應(yīng)的todolist curMenu: {}, //選中的目錄 menus: [] //所有目錄列表 }
目錄對應(yīng)的todo列表的切換,我采用的方式是:curMenu只要一有變動就會向后端發(fā)送請求,后臺返回該目錄下對應(yīng)的所有todo列表,更新到todos。因此,這里只設(shè)計了一個todos狀態(tài)就行了。
mutations設(shè)計mutation只能同步,無法異步,因此mutations.js只設(shè)計狀態(tài)的改變。此處根據(jù)交互有這幾處涉及到的狀態(tài)改變:
向todos中添加todo
在todos中刪除選中的todo
更新選中todo的完成狀態(tài)
設(shè)置當前目錄對應(yīng)的todos數(shù)組
設(shè)置所有的目錄列表menus
設(shè)置當前選中的目錄curMenu
對應(yīng)的js代碼如下:
export const mutations = { //添加todo addTodo(state, {todo}) { state.todos.push(todo) }, //刪除todos deleteTodo(state, {todo}) { state.todos.splice(state.todos.indexOf(todo), 1); }, //設(shè)置當前的todos setTodo(state, {todos}) { state.todos = todos }, //切換todo的完成狀態(tài) toggleTodo(state, {todo}) { todo.done = !todo.done; }, //左側(cè)menu切換,設(shè)置當前menu值 setCurMenu(state, {menu}) { state.curMenu = menu; }, //設(shè)置menu值, getMenu(state, {menus}) { state.menus = menus; } }acitions設(shè)計
由于需要后臺存儲一些todo列表的狀態(tài),需要將一些動作的變動告知后臺,后臺更新相應(yīng)的數(shù)據(jù)庫信息,需要添加actions。
這里要注意:actions.js主要放置一些與后臺交互相關(guān)的操作,而mutations.js只用作狀態(tài)的改變。
仔細看看交互,會發(fā)現(xiàn)這里存在以下幾個動作:
獲取所有的目錄,即獲得menus列表
切換menu時,向后臺獲取對應(yīng)的todolist,并更新對應(yīng)todos列表
用戶添加list時,向后臺發(fā)送請求存儲在數(shù)據(jù)庫中
用戶刪除list時,向后臺發(fā)送請求將數(shù)據(jù)庫中該條記錄刪除
用戶改變todolist中的某個list的狀態(tài)時,向后臺發(fā)送請求更新數(shù)據(jù)庫中該條記錄的done字段
import axios from "axios"; const host = "http://localhost:3001" //獲取所有的menu export const getMenus = ({commit}) => { axios.get(host + "/menu/get") .then((response) => { var data = response.data; var initMenu data.forEach(function(item) { if(item.selected) { initMenu = item } }) commit("getMenu", {menus: data}); //提交mutation,初始化menus列表 commit("setCurMenu", {menu: initMenu}); //提交mutation,初始化curMenu。 axios.get(host + "/todolist/get/"+initMenu.id) .then((response) => { var data = response.data; commit("setTodo", {todos: data}) //根據(jù)初始化的curMenu獲取todolist,并提交mutation更新todos列表 }) .catch( error => { console.log(error) }) }) .catch( error => { console.log(error) }) } //確切的講是getCurTodoList,得到當前menu對應(yīng)的todolist export const setCurMenu = ({commit}, menuData) => { axios.get(host + "/todolist/get/"+menuData.id) .then((response) => { var data = response.data; commit("setTodo", {todos: data}) //用數(shù)據(jù)庫返回的數(shù)據(jù)提交mutation,更新todo列表 commit("setCurMenu", {menu: menuData.menu}) //更新當前目錄,提交mutation來更新curMenu值 }) .catch( error => { console.log(error) }) } //添加todo export const addTodo = ({commit}, data) => { axios.post(host + "/todolist/add", { data: data, }) .then((response) => { commit("addTodo", {todo: response.data}) // 提交mutation,向todos中添加數(shù)據(jù)庫返回的記錄 }) .catch( error => { console.log(error) }) } //刪除todo export const deleteTodo = ({commit}, todo) => { axios.post(host + "/todolist/delete", { id: todo.todo.id, }) .then((response) => { commit("deleteTodo", todo) //提交mutation,在todos中刪除該條記錄 }) .catch( error => { console.log(error) }) } //完成與未完成的切換 export const toggleTodo = ({commit}, todo) => { axios.post(host + "/todolist/toggle", { id: todo.todo.id, done: todo.todo.done }) .then((response) => { console.log(response) commit("toggleTodo", {todo: todo.todo}) //提交mutation,更新該條todo的完成狀態(tài) }) .catch( error => { console.log(error) }) }getters
細心的同學會發(fā)現(xiàn),我的界面里面分門別類的顯示了已完成和未完成的類別,因此需要通過getters來根據(jù)todos的數(shù)據(jù)獲得對應(yīng)的數(shù)據(jù)
export const doneTodos = state => { return state.todos.filter(item => item.done) } export const undoneTodos = state => { return state.todos.filter( item => !item.done) }組件 頁面組件
這里我就不多贅述了,都是組件相關(guān)的概念,要細講的話概念點就太多了,看不懂的建議大家去vue官網(wǎng)學習相關(guān)概念再來看。
index.vue
{{isShowDone ? "隱藏已完成任務(wù)" : "顯示已完成任務(wù)"}} 共{{doneTodos.length}}項
這里的mapState和mapGetters等都在vuex官網(wǎng)里面有介紹,前面的三點...是es6的語法對象展開符,不懂的童鞋可google一下,網(wǎng)上一堆教程。
我們可以看到,這個頁面我用到了menus組件和todo-list組件,這里我遇到過一個坑,vue文件的模板template中的元素只能有一個根節(jié)點,不能有多個根節(jié)點,大家寫的時候在只在頂層寫一個標簽就好了。
menus組件menus.vue
menu-item.vue
todo-list組件
todo-list.vue
總結(jié){{todo.text}}
至此,主要的功能結(jié)構(gòu)都講的差不多了,有哪里講的不清楚的地方,還望指出來,互相學習。
不得不說,vue框架用來來真的不錯,大家在開發(fā)的時候,記住:要從數(shù)據(jù)的角度思考問題,一切就會變得如此簡單。
這里記錄下接下來要添加的功能,如果有踩坑會繼續(xù)帶來博文分享互相交流學習下:
頂部路由添加user登錄信息
頂部多帶帶辟出一個路由顯示已完成任務(wù)或其他主題
左側(cè)menu欄目添加新建目錄功能,其實后端接口已經(jīng)寫好,前端需要加一下
左側(cè)每個目錄需要有右鍵功能,右鍵彈出項目暫定: 重命名、刪除
右側(cè)添加任務(wù)的界面要改的好一點,樣式細節(jié)需要調(diào)整
右側(cè)輸入框目前是點擊回車之后會自動添加一個項目,但是在中文輸入法時直接按回車切換英文時存在bug,會導致list存在空白的情況,這個要處理一下
每個list是否需要可以編輯待定
左側(cè)目錄最右端顯示有幾條todolist功能添加
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/82759.html
摘要:它會檢測出最大靜態(tài)子樹就是不需要動態(tài)性的子樹并且從渲染函數(shù)中萃取出來。這樣在每次重渲染的時候,它就會直接重用完全相同的同時跳過比對。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個鏈接~手把手教你從零寫一個簡單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非常火的vue2前端框架的特點和vue2+vuex2+we...
摘要:它會檢測出最大靜態(tài)子樹就是不需要動態(tài)性的子樹并且從渲染函數(shù)中萃取出來。這樣在每次重渲染的時候,它就會直接重用完全相同的同時跳過比對。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個鏈接~手把手教你從零寫一個簡單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非常火的vue2前端框架的特點和vue2+vuex2+we...
摘要:它會檢測出最大靜態(tài)子樹就是不需要動態(tài)性的子樹并且從渲染函數(shù)中萃取出來。這樣在每次重渲染的時候,它就會直接重用完全相同的同時跳過比對。需要注意的是,中的操作必須是同步的,不可以存在異步操作的情況。 新增:哈哈,最近又推出了 vue 的文章,在這里放個鏈接~手把手教你從零寫一個簡單的 VUE 感謝有人看我扯技術(shù),這篇文章主要介紹最近非常火的vue2前端框架的特點和vue2+vuex2+we...
摘要:五六月份推薦集合查看最新的請點擊集前端最近很火的框架資源定時更新,歡迎一下。蘇幕遮燎沈香宋周邦彥燎沈香,消溽暑。鳥雀呼晴,侵曉窺檐語。葉上初陽乾宿雨,水面清圓,一一風荷舉。家住吳門,久作長安旅。五月漁郎相憶否。小楫輕舟,夢入芙蓉浦。 五、六月份推薦集合 查看github最新的Vue weekly;請::點擊::集web前端最近很火的vue2框架資源;定時更新,歡迎 Star 一下。 蘇...
閱讀 1053·2021-09-22 15:26
閱讀 2627·2021-09-09 11:52
閱讀 1920·2021-09-02 09:52
閱讀 2256·2021-08-12 13:28
閱讀 1195·2019-08-30 15:53
閱讀 523·2019-08-29 13:47
閱讀 3394·2019-08-29 11:00
閱讀 3106·2019-08-29 10:58