-
-
Save yyx990803/762ec427882a61be3e4affe02f8af555 to your computer and use it in GitHub Desktop.
<template> | |
<div id="demo"> | |
<h1>Latest Vue.js Commits</h1> | |
<template v-for="branch in branches"> | |
<input type="radio" | |
:id="branch" | |
:value="branch" | |
name="branch" | |
v-model="currentBranch"> | |
<label :for="branch">{{ branch }}</label> | |
</template> | |
<p>vuejs/vue@{{ currentBranch }}</p> | |
<ul> | |
<li v-for="record in commits"> | |
<a :href="record.html_url" target="_blank" class="commit">{{ record.sha.slice(0, 7) }}</a> | |
- <span class="message">{{ record.commit.message | truncate }}</span><br> | |
by <span class="author"><a :href="record.author.html_url" target="_blank">{{ record.commit.author.name }}</a></span> | |
at <span class="date">{{ record.commit.author.date | formatDate }}</span> | |
</li> | |
</ul> | |
</div> | |
</template> | |
<script> | |
// filters are omitted since they are the same | |
const apiURL = 'https://api.github.com/repos/vuejs/vue/commits?per_page=3&sha=' | |
function fetch(url, cb) { | |
const xhr = new XMLHttpRequest() | |
xhr.open('GET', apiURL + url) | |
xhr.onload = function () { | |
cb(JSON.parse(xhr.responseText)) | |
} | |
xhr.send() | |
} | |
// 2.x | |
const oldAPI = { | |
data() { | |
return { | |
branches: ['master', 'dev'], | |
currentBranch: 'master', | |
commits: null | |
} | |
}, | |
created: function () { | |
this.fetchData() | |
}, | |
watch: { | |
currentBranch: 'fetchData' | |
}, | |
methods: { | |
fetchData: function () { | |
fetch(this.currentBranch, data => { | |
this.commits = data | |
}) | |
} | |
} | |
} | |
const newAPI = { | |
setup() { | |
const currentBranch = value('master') | |
const commits = value(null) | |
watch(currentBranch, branch => { | |
fetch(branch, data => { | |
commits.value = data | |
}) | |
}) | |
return { | |
branches: ['master', 'dev'], | |
currentBranch, | |
commits | |
} | |
} | |
} | |
</script> |
<template> | |
<div> | |
Count is {{ count }}, count * 2 is {{ double }} | |
<button @click="increment">+</button> | |
</div> | |
</template> | |
<script> | |
const oldAPI = { | |
data() { | |
return { | |
count: 0 | |
} | |
}, | |
methods: { | |
increment() { | |
this.count++ | |
} | |
}, | |
computed: { | |
double() { | |
return this.count * 2 | |
} | |
} | |
} | |
const newAPI = { | |
setup() { | |
const count = value(0) | |
const double = computed(() => count.value * 2) | |
const increment = () => { count.value++ } | |
return { | |
count, | |
double, | |
increment | |
} | |
} | |
} | |
</script> |
<template> | |
<table v-if="filteredData.length"> | |
<thead> | |
<tr> | |
<th v-for="key in columns" | |
@click="sortBy(key)" | |
:class="{ active: sortKey == key }"> | |
{{ key | capitalize }} | |
<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"> | |
</span> | |
</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr v-for="entry in filteredData"> | |
<td v-for="key in columns"> | |
{{entry[key]}} | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
<p v-else>No matches found.</p> | |
</template> | |
<script> | |
const oldAPI = { | |
props: { | |
data: Array, | |
columns: Array, | |
filterKey: String | |
}, | |
data: function () { | |
var sortOrders = {} | |
this.columns.forEach(function (key) { | |
sortOrders[key] = 1 | |
}) | |
return { | |
sortKey: '', | |
sortOrders: sortOrders | |
} | |
}, | |
computed: { | |
filteredData: function () { | |
var sortKey = this.sortKey | |
var filterKey = this.filterKey && this.filterKey.toLowerCase() | |
var order = this.sortOrders[sortKey] || 1 | |
var data = this.data | |
if (filterKey) { | |
data = data.filter(function (row) { | |
return Object.keys(row).some(function (key) { | |
return String(row[key]).toLowerCase().indexOf(filterKey) > -1 | |
}) | |
}) | |
} | |
if (sortKey) { | |
data = data.slice().sort(function (a, b) { | |
a = a[sortKey] | |
b = b[sortKey] | |
return (a === b ? 0 : a > b ? 1 : -1) * order | |
}) | |
} | |
return data | |
} | |
}, | |
methods: { | |
sortBy: function (key) { | |
this.sortKey = key | |
this.sortOrders[key] = this.sortOrders[key] * -1 | |
} | |
} | |
} | |
const newAPI = { | |
props: { | |
data: Array, | |
columns: Array, | |
filterKey: String | |
}, | |
setup(props) { | |
const sortKey = value('') | |
const sortOrders = value({}) | |
props.columns.forEach(key => { | |
sortOrders.value[key] = 1 | |
}) | |
const filteredData = computed(() => { | |
const filterKey = props.filterKey && props.filterKey.toLowerCase() | |
const sortKeyValue = sortKey.value | |
const order = sortOrders.value[sortKeyValue] || 1 | |
let data = props.data | |
if (filterKey) { | |
data = data.filter(row => { | |
return Object.keys(row).some(key => { | |
return String(row[key]).toLowerCase().indexOf(filterKey) > -1 | |
}) | |
}) | |
} | |
if (sortKeyValue) { | |
data = data.slice().sort((a, b) => { | |
a = a[sortKeyValue] | |
b = b[sortKeyValue] | |
return (a === b ? 0 : a > b ? 1 : -1) * order | |
}) | |
} | |
return data | |
}) | |
function sortBy(key) { | |
sortKey.value = key | |
sortOrders.value[key] = sortOrders.value[key] * -1 | |
} | |
return { | |
sortKey, | |
sortOrders, | |
filteredData, | |
sortBy | |
} | |
} | |
} | |
</script> |
<template> | |
<div> | |
<section class="todoapp"> | |
<header class="header"> | |
<h1>todos</h1> | |
<input class="new-todo" | |
autofocus autocomplete="off" | |
placeholder="What needs to be done?" | |
v-model="newTodo" | |
@keyup.enter="addTodo"> | |
</header> | |
<section class="main" v-show="todos.length" v-cloak> | |
<input class="toggle-all" type="checkbox" v-model="allDone"> | |
<ul class="todo-list"> | |
<li v-for="todo in filteredTodos" | |
class="todo" | |
:key="todo.id" | |
:class="{ completed: todo.completed, editing: todo == editedTodo }"> | |
<div class="view"> | |
<input class="toggle" type="checkbox" v-model="todo.completed"> | |
<label @dblclick="editTodo(todo)">{{ todo.title }}</label> | |
<button class="destroy" @click="removeTodo(todo)"></button> | |
</div> | |
<input class="edit" type="text" | |
v-model="todo.title" | |
v-todo-focus="todo == editedTodo" | |
@blur="doneEdit(todo)" | |
@keyup.enter="doneEdit(todo)" | |
@keyup.esc="cancelEdit(todo)"> | |
</li> | |
</ul> | |
</section> | |
<footer class="footer" v-show="todos.length" v-cloak> | |
<span class="todo-count"> | |
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left | |
</span> | |
<ul class="filters"> | |
<li><a href="#/all" :class="{ selected: visibility == 'all' }">All</a></li> | |
<li><a href="#/active" :class="{ selected: visibility == 'active' }">Active</a></li> | |
<li><a href="#/completed" :class="{ selected: visibility == 'completed' }">Completed</a></li> | |
</ul> | |
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining"> | |
Clear completed | |
</button> | |
</footer> | |
</section> | |
<footer class="info"> | |
<p>Double-click to edit a todo</p> | |
<p>Written by <a href="http://evanyou.me">Evan You</a></p> | |
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> | |
</footer> | |
</div> | |
</template> | |
<script> | |
import { computed } from "../../packages/reactivity/src"; | |
var STORAGE_KEY = 'todos-vuejs-2.0' | |
var todoStorage = { | |
fetch: function () { | |
// omitted since they are the same across implementations | |
}, | |
save: function (todos) { | |
// omitted since they are the same across implementations | |
} | |
} | |
// visibility filters | |
var filters = { | |
// omitted since they are the same across implementations | |
} | |
const oldAPI = { | |
// app initial state | |
data: { | |
todos: todoStorage.fetch(), | |
newTodo: '', | |
editedTodo: null, | |
visibility: 'all' | |
}, | |
// watch todos change for localStorage persistence | |
watch: { | |
todos: { | |
handler: function (todos) { | |
todoStorage.save(todos) | |
}, | |
deep: true | |
} | |
}, | |
// computed properties | |
// https://vuejs.org/guide/computed.html | |
computed: { | |
filteredTodos: function () { | |
return filters[this.visibility](this.todos) | |
}, | |
remaining: function () { | |
return filters.active(this.todos).length | |
}, | |
allDone: { | |
get: function () { | |
return this.remaining === 0 | |
}, | |
set: function (value) { | |
this.todos.forEach(function (todo) { | |
todo.completed = value | |
}) | |
} | |
} | |
}, | |
// methods that implement data logic. | |
// note there's no DOM manipulation here at all. | |
methods: { | |
addTodo: function () { | |
var value = this.newTodo && this.newTodo.trim() | |
if (!value) { | |
return | |
} | |
this.todos.push({ | |
id: todoStorage.uid++, | |
title: value, | |
completed: false | |
}) | |
this.newTodo = '' | |
}, | |
removeTodo: function (todo) { | |
this.todos.splice(this.todos.indexOf(todo), 1) | |
}, | |
editTodo: function (todo) { | |
this.beforeEditCache = todo.title | |
this.editedTodo = todo | |
}, | |
doneEdit: function (todo) { | |
if (!this.editedTodo) { | |
return | |
} | |
this.editedTodo = null | |
todo.title = todo.title.trim() | |
if (!todo.title) { | |
this.removeTodo(todo) | |
} | |
}, | |
cancelEdit: function (todo) { | |
this.editedTodo = null | |
todo.title = this.beforeEditCache | |
}, | |
removeCompleted: function () { | |
this.todos = filters.active(this.todos) | |
} | |
} | |
} | |
const newAPI = { | |
setup() { | |
const todos = value(todoStorage.fetch()) | |
const newTodo = value('') | |
const editedTodo = value(null) | |
const visibility = value('all') | |
let beforeEditCache = '' | |
watch(todos, todos => { | |
todoStorage.save(todos) | |
}, { deep: true }) | |
const computeds = { | |
filteredTodos: computed(() => filters[visibility.value](todos.value)), | |
remaining: computed(() => filters.active(todos.value).length), | |
allDone: computed( | |
() => computeds.remaining.value === 0, | |
() => todos.value.forEach(todo => { | |
todo.completed = true | |
}) | |
) | |
} | |
const methods = { | |
addTodo() { | |
const value = newTodo.value && newTodo.value.trim() | |
if (value) { | |
todos.value.push({ | |
id: todoStorage.uid++, | |
title: value, | |
completed: false | |
}) | |
} | |
newTodo.value = '' | |
}, | |
removeTodo(todo) { | |
todos.value = todos.value.filter(t => t !== todo) | |
}, | |
editTodo(todo) { | |
beforeEditCache = todo.title | |
editedTodo.value = todo | |
}, | |
doneEdit(todo) { | |
if (editedTodo.value) { | |
editedTodo.value = null | |
todo.title = todo.title.trim() | |
if (!todo.title) { | |
methods.removeTodo(todo) | |
} | |
} | |
}, | |
cancelEdit() { | |
todo.title = todo.title.trim() | |
if (!todo.title) { | |
methods.removeTodo(todo) | |
} | |
}, | |
removeCompleted() { | |
todos.value = filters.active(todos.value) | |
} | |
} | |
return { | |
todos, | |
newTodo, | |
editedTodo, | |
visibility, | |
...computeds, | |
...methods | |
} | |
} | |
} | |
</script> |
In the todomvc example,
beforeEditCache
state is not declared in the old api section.
Because it's not meant to be reactive. In 2.x you need to use an instance property to share a value between methods, while in 3.x you can just use a simple variable declared in the outer scope
Yes, but cancelEdit is different in your 3.x example and doesn't restore the cache value.
Hello! How can I use Vuex and Vue-router inside setup function? Thanks!
Here starts the decline of a good languge 😞
I was ready to be outraged by a syntax change. But I can honestly see the benefits of the new API. Although it may have a slightly increased learning curve for beginners. Either way people rarely take the time to learn how to best structure their components and to make the best use of Vue architecture.
In the todomvc example,
beforeEditCache
state is not declared in the old api section.