image source
上次的文章中是我以前常用的寫法,而今天要說的是我前陣子看到這篇文章發現的新大陸
[week 21] 前端框架 - 先別急著學 React - HackMD
我覺得挺有趣的就試著把上次那版改成這種方式下去實作!
Django的程式碼跟上週一樣所以今天不會有python的code,就請參考上篇文章!!
那這次我是使用axios跟fetch大同小異,只是需要而外安裝(引入)也有使用到一些JQuery,話不多說先上code吧~
<script src="https://code.jquery.com/jquery-3.7.0.js" integrity="sha256-JlqSTELeR4TLqP0OG9dxM7yDPqX1ox/HfgiSLBj8+kM=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
//....
let state = {
todos: []
}
function get_all_list(){
axios.get("/api/")
.then(response => {
response.data.data.forEach(todo => {
state = {
todos: [...state.todos, {
id: todo.id,
content: todo.title,
isDone: todo.complete
}]
}
})
updateState(state)
})
}
// 更新 state
function updateState(newState) {
state = newState;
render()
}
// state => UI
function render() {
// 先把畫面清空
$('.todos').empty();
console.log(state.todos)
$('.todos').append(
// 把每個 todo 的 HTML 集合起來放到畫面上
state.todos.map(todo => Todo(todo)).join('')
);
}
// Todo component
function Todo({id, content, isDone}) {
return `
<div class="ui segment todo" data-id="${id}">
<p class="ui big header"> ${id} | ${content} </p>
${Span({
className: isDone ? 'ui green label' : 'ui gray label',
content: isDone ? 'Complete' : 'Not Completed'
})}
${Button({
className: 'blue btn-update',
content: 'Update'
})}
${Button({
className: 'red btn-delete',
content: 'Delete'
})}
</div>
`
}
// Span component
function Span(props){
return `<span class="${props.className}">${props.content}</span>`
}
// Button component
function Button(props) {
return `
<a class="ui ${props.className} button">${props.content}</a>
`
}
// 新增 todo
$('.btn-add').click(() => {
const content = $('.input-todo').val();
if (!content) return;
$('.input-todo').val('');
axios.post("/api/add/",
{
"title": content
},
{
headers: {
"X-CSRFToken": "{{csrf_token}}",
},
}
)
.then(response => {
todo_id = response.data["todo_id"]
title = response.data["todo_title"]
complete = response.data["complete"]
// 更新 state
updateState({
todos: [...state.todos, {
id: todo_id,
content: title,
isDone: complete
}]
})
})
})
// 刪除 todo
$('.todos').on('click', '.btn-delete', e => {
const id = Number($(e.target).parents('.todo').attr('data-id'));
axios.get("/api/delete/"+id)
.then(response => {
d_id = response.data["todo_id"]
updateState({
todos: state.todos = state.todos.filter(todo => todo.id !== d_id)
})
})
})
// 未完成 <-> 已完成
$('.todos').on('click', '.btn-update', e => {
const id = Number($(e.target).parents('.todo').attr('data-id'));
axios.get("/api/update/"+id)
.then(response => {
u_id = response.data["todo_id"]
complete = response.data["complete"]
updateState({
todos: state.todos.map(todo => {
if (todo.id !== u_id) return todo;
return {
...todo,
isDone: complete
}
})
})
})
})
跟上次相比是不是很不一樣,我自己覺得這樣子的寫法更加的直觀和易讀易懂!
那我們一樣拆開來看,首先我們要生成Todo的component
// Todo component
function Todo({id, content, isDone}) {
return `
<div class="ui segment todo" data-id="${id}">
<p class="ui big header"> ${id} | ${content} </p>
${Span({
className: isDone ? 'ui green label' : 'ui gray label',
content: isDone ? 'Complete' : 'Not Completed'
})}
${Button({
className: 'blue btn-update',
content: 'Update'
})}
${Button({
className: 'red btn-delete',
content: 'Delete'
})}
</div>
`
}
// Span component
function Span(props){
return `<span class="${props.className}">${props.content}</span>`
}
// Button component
function Button(props) {
return `
<a class="ui ${props.className} button">${props.content}</a>
`
}
我的Todo component裡面還包括了一個Span component和兩個Button component那他們會依據帶進去的參數而有不同的樣式
接著再到get_all_list()
let state = {
todos: []
}
function get_all_list(){
axios.get("/api/")
.then(response => {
response.data.data.forEach(todo => {
state = {
todos: [...state.todos, {
id: todo.id,
content: todo.title,
isDone: todo.complete
}]
}
})
updateState(state)
})
}
// 更新 state
function updateState(newState) {
state = newState;
render()
}
// state => UI
function render() {
// 先把畫面清空
$('.todos').empty();
console.log(state.todos)
$('.todos').append(
// 把每個 todo 的 HTML 集合起來放到畫面上
state.todos.map(todo => Todo(todo)).join('')
)
}
一開始的狀態先給一個空array,在get_all_list()用axios去打api拿取現在所有的Todo datas,拿到datas後在一個一個把他們塞進去todos array裡面,最後再交由updateState去把現在的state更新掉然後render,render()的工作很簡單會先把現在html上所有的todos元素清空,然後在一筆一筆塞進去~
再來我們來看看新增
// 新增 todo
$('.btn-add').click(() => {
const content = $('.input-todo').val();
if (!content) return;
$('.input-todo').val('');
axios.post("/api/add/",
{
"title": content
},
{
headers: {
"X-CSRFToken": "{{csrf_token}}",
},
}
)
.then(response => {
todo_id = response.data["todo_id"]
title = response.data["todo_title"]
complete = response.data["complete"]
// 更新 state
updateState({
todos: [...state.todos, {
id: todo_id,
content: title,
isDone: complete
}]
})
})
})
很簡單的去判斷button有沒有沒click,然後取input的值丟axios,那response會回傳該todo的data,就把他updateState一次就OK了!
接下來的修改和刪除也是同樣的概念,打api後response丟給updateState就完事啦~
// 刪除 todo
$('.todos').on('click', '.btn-delete', e => {
const id = Number($(e.target).parents('.todo').attr('data-id'));
axios.get("/api/delete/"+id)
.then(response => {
d_id = response.data["todo_id"]
updateState({
todos: state.todos = state.todos.filter(todo => todo.id !== d_id)
})
})
})
// 未完成 <-> 已完成
$('.todos').on('click', '.btn-update', e => {
const id = Number($(e.target).parents('.todo').attr('data-id'));
axios.get("/api/update/"+id)
.then(response => {
u_id = response.data["todo_id"]
complete = response.data["complete"]
updateState({
todos: state.todos.map(todo => {
if (todo.id !== u_id) return todo;
return {
...todo,
isDone: complete
}
})
})
})
})
刪除就是把存在state裡的todo id移除掉,而修改則是把該todo id抓出來改變他的isDone屬性。
至此就大功告成啦,對Javascript不熟悉的我經過這個練習大概可以知道state component的概念!下次可能就是直接用react改寫看看囉!
軟體和程式的世界真的很有趣,可以用不同的做法達到相同的目的,而且瞬息萬變,可能明天又能有新的東西可以學習,想想就興奮呢!!