Giter Site home page Giter Site logo

notes's People

Contributors

debbygigigi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

noiredist

notes's Issues

20190217

Bootstrap 4.3 釋出,準備和 jQuery 說掰掰?

https://blog.getbootstrap.com/2019/02/11/bootstrap-4-3-0/

除了新增或修改ㄧ些 class 名稱之外,這一版本有一些變動:

  1. responsive font sizes
    終於針對字級有 RWD 了!不然每次都要針對畫面的字級下 Media query
    他們是引入他們團隊的另一個特別針對字級做 RWD 的專案:rfs
    https://github.com/twbs/rfs
    這個功能預設是關閉的,如果需要要將 $enable-responsive-font-sizes 變數打開

  2. Move to Hugo
    一直以來 Bootstrap 官網都是用 Jekyll 架站的,打算轉移到 Hugo https://gohugo.io/

  3. dropping jQuery for regular JavaScript
    twbs/bootstrap#23586
    準備在v5版本完全的移除 jQuery

https://thispersondoesnotexist.com/

State of css in 2019 問卷調查

https://stateofcss.com/

既 State of Js,當然也有 css
不過目前還在問卷調查的階段,有興趣的人可以做做看

demo
https://codepen.io/MartijnCuppens/pen/ZBjdMy?editors=1000

在Vue裡使用setTimeInteval

原本我是這樣寫的

data() {
    return {
        second: 120
    }
},
methods: {
    countdown() {
         window.clearInterval(countdown);
            let countdown = window.setInterval(() => {
                this.second--;
                if(this.second <= 0) {
                    window.clearInterval(countdown);
                }
            }, 1000);
    }
}

第一次call 這個 function 還好,但在同一頁面在call一次時很明顯速度就不對

1522408783853

後來將 window.setInterval 存在Vue 的 data 就可以了
不過不清楚原因

data() {
    return {
        second: 120,
        timer: null
    }
},
methods: {
    countdown() {
         window.clearInterval(this.timer);
            this.timer = window.setInterval(() => {
                this.second--;
                if(this.second <= 0) {
                    window.clearInterval(this.timer);
                }
            }, 1000);
    }
}

[Daily Report] April 2019

4/1

忙寫專案

  1. 發現好用的 vscode 套件:Live Sass Compiler
    之前寫小專案時,都要另外用 Prepros 做 scss 的編譯,每次要編譯都要另外開啟也是蠻麻煩的,而且免費版還會一直跳視窗...
    總之這個套件真的太方便了

  2. table with border radius

參考這篇:https://pjchender.blogspot.com/2015/12/table-with-border-radius.html 寫得很清楚
順便複習了一些 table 的語法
demo:https://codepen.io/dbjjj/pen/wZaJQw?editors=0100

  1. April Fool's Day 愚人節快樂!

好喜歡 Stackoverflow 推出的愚人節彩蛋
https://www.facebook.com/100000402378796/videos/2290845347605522/?id=100000402378796

跑馬燈、gif 動畫、滑鼠特效、repeat 的 background-image、瀏覽人數、Comic Sans 字體,真的很 old school!

swiper 資料更動後 lazy load 不能loaded的問題

問題:
我有一個頁面裡面有很多利用ajax data render出來的卡片,每張卡片上都有用 swiper 來輪播照片並使用swiper套件提供的 lazy load
在同一個畫面中有動作會觸發資料變動,render出來的卡片也跟著變動,但奇怪的事來了,變動的卡片lazy load似乎沒有執行,一直停留在loading的畫面

  1. 發現:重新render後,卡片從6張變成10張,發現 mounted 會執行的只有多出來的那4張
  2. 發現:只有在 mounted 的時候,swiperlazy load 才有作用,

解決:資料更動後,會觸發的地方是在 updated 時,所以在這裡執行 mySwiper.lazy.load(); 即可觸發
lazy load 事件

2019/3/11 - Write Number in Expanded Form

題目:Write Number in Expanded Form

ps. Expanded Form

example:

expandedForm(12); // Should return '10 + 2'
expandedForm(42); // Should return '40 + 2'
expandedForm(70304); // Should return '70000 + 300 + 4'

My answer:

function expandedForm(num) {
  let string = [];
  let number = num;
  for(let i = String(num).length - 1; i >= 0; i--) {
    if(Math.floor(number/10**i)*(10**i) !== 0) {
      string.push(Math.floor(number/10**i)*(10**i));
    }
    number = number - Math.floor(number/10**i)*(10**i);
  }
  return string.join(' + ');
}

Best Practice:

const expandedForm = n => n.toString()
                            .split("")
                            .reverse()
                            .map( (a, i) => a * Math.pow(10, i))
                            .filter(a => a > 0)
                            .reverse()
                            .join(" + ");

vue directive 使用

directive (n.) 指令

vue directive 就是使用 v- 作為前綴的指令,主要用來操作 DOM
目前最常用到的就是官方提供的 directive: v-ifv-for 等等
而官方也有提供 custom directive 讓我們撰寫客製的 directive

Name of Directive

一個基本的 directive 至少會有 name,可以沒有 arguments 或 modifiers,像是官方提供的 v-else

<app-navigation v-sticky></app-navigation>

Passing Values to Directive

你可以傳值給 directive

<div v-if="isVisible">Show this</div>

Arguments

<app-navigation v-sticky:bottom></app-navigation>

the name of directive is sticky

一個 directive 只能有一個 Arguments

Modifiers

修飾符,主要會利用 . 串連在 name 後方,能讓 directive 再多一點不一樣的變化

<span v-format.underline>guide</span>

修飾符可以多個:

<span v-format.bold.highlight.underline>guide</span>

上例,modifiers 的值會是 boldhighlightunderline 三個都有


example: v-scroll

來寫一個 scroll 的 directive!

<div v-scroll="handleScroll"></div>

這個 v-scroll 主要的功用是偵測該 element 有 scroll 的行為就觸發 handleScroll 事件(不一定要 handleScroll 總之會連動到我們給他的事件就好)

一個非常簡單的 directive

Vue.directive("scroll", function(el, binding, vnode) {
 if (binding.value) {
     window.removeEventListener("scroll", binding.value);
 }
 window.addEventListener("scroll", binding.value);
});

這裡的 binding.value 就是我們給他的事件 handleScroll,所以我們讓這個 directive 初始化時會觸發 window.addEventListener("scroll", binding.value); 的事件,在綁定前先移除之前綁定的 EventListener

resources

Dynamic components [Draft]

#主要是因為公司要做活動網頁,考量到未來可能會有多個活動網頁
希望在這個 Event.vue 下去切換當前頁面所需要的 components

首先在路徑也是採用參數的方式,這樣的寫法,假設路徑為 .../events/2018-04/Hello,則這個 blade 便會得到 month2018-04nameHello

Route::get('events/{month}/{name}', function ($month, $name) {
        return view('events', [
            'month' => $month,
            'name' => $name
        ]);
    });

在 blade 中,我們引入一個 Event 的 Component,並給予他參數
然後載入 Events.js

Events.blade.php

...
<Event month="{{ $month }}" name="{{ $name }}"></Event>
...
<script src="{{ mix('js/Events.js') }}"></script>

在Event.js裡,很簡單的只載入 Event 這個 Components

const Event = {
    components: {
        Event: require('@/Pages/Event')
    }
};

mixins.push(Event);

然後就是重點
採用 Dynamic components

Event.vue

<template>
    <component :is="event"/>
</template>

我們希望透過event這個變數來給予更換不同的components
而這個event是透過擷取網址上的變數來找到這是哪個components

...
computed: {
        event () {
            return () => import(`@/Pages/Events/${this.month}/${this.camelCasedName}/index.vue`);
        }
    },

Vue 源碼分析(一):響應式

該來理解一下 Vue 的響應式系統是如何實作的了!

<div id="app" @click="changeMsg">
  {{ message }}
</div>
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  },
  methods: {
    changeMsg() {
      this.message = 'Hello World!'
    }
  }
})

當我們修改 this.message 時,模板裡的 message 也會跟著改變,這是怎麼做到的?

在 Vue 的 文件 裡有提到

当一个 Vue 实例被创建时,它将 data 对象中的所有的属性加入到 Vue 的响应式系统中

可以想像成,vue 會把 data 裡的每個值都裝上監聽器,如果值有改變就會觸發一些動作,例如更新 view 之類的。

裝上監聽器:getter/setter

先來看一個範例,有 a、b 兩值,b 為 a 的兩倍,當 a 變動時,b 還是原來的 20。

let a = 10
let b = a * 2
console.log(b) // 20
a = 20
console.log(b) // 20

為什麼 a 改變時, b 沒有跟著改變呢?因為沒有人通知它,所以 b 拿到的 a 還是過去的 a。

接下來我們就使用 Object.defineProperty 來裝上監聽器,更準確的說轉化成 getter/setter

實作 Object.defineProperty

假設有一個 data 的 object

const data = {
  a: 10
}

我們想讓 data.a 這個值能被監聽

let temp = data.a

Object.defineProperty(data, 'a', {
    get () {
        console.log(`getting key "a": ${temp}`)
        return temp
    },
    set (newValue) {
        console.log(`setting key "a" to ${temp}`)
        temp = newValue
    }
})

在 Object.defineProperty
第一個參數是放 object
第二個參數是放要監聽的 property
最重要的是後面的 getter 和 setter

這邊要注意的是,ㄧ定要把值用另一個變數存起來
像我一開始 get return 的是他自己 data.a
這樣會造成無限循環的 loop

實際跑跑看,可以看到 get 和 set 值的時候都有 console.log,這樣就能繼續做後續的監聽跟通知

到這裡你可能會好奇,咦 Object.defineProperty 是怎麼做到的

在這張圖中,上面的 state 是有經過 Object.defineProperty 包裝的,而下面的 state2 沒有,是一般的 object。可以看到 state 很不一樣的多了 getset 函數,這個就是我們在 Object.defineProperty 裡所寫的 getset

讓我們來看看 Vue 源碼如何實作,這邊有稍微簡化程式碼
程式碼參考 這裡

function observe(value, cb) {
    Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

function defineReactive (obj, key, val, cb) {
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>{
            /*....依赖收集(後面會提到)....*/
            return val
        },
        set: newVal=> {
            val = newVal;
            cb();/*订阅者收到消息的回调*/
        }
    })
}

class Vue {
  constructor(option) {
    this._data = options.data;
    observe(this._data, options.render)
  }
}

new Vue({
    el: '#app',
    data: {
        text: 'text',
        text2: 'text2'
    },
    render(){
        console.log("render");
    }
})

Vue 在 initState 時呼叫了 observe 這個 function ,將 _data 變成 observed,所以當 _data 值變動時就會觸發 set 告訴訂閱者(後面會講到),這邊以 render 表示觸發畫面更新

因為我們這邊沒有考慮 Array 等情況,所以簡化許多

我們實際運行這段程式碼,當改變 data 的值時,就會呼叫 render

不過我們操作 data 的值都要透過 app._data 不太方便,我們希望透過 app.text 就能操作,這時就要使用 proxy 代理

proxy 代理

proxy 的概念就是,以我們的例子來說,當我們用 app.text 取值時,會先透過 proxy 把這個動作改成 app._data.text

這邊也是簡單實作,程式碼參考 這裡

class Vue {
  constructor(options) {
    this._data = options.data
    observe(this._data, options.render)
    _proxy.call(this, options.data);
  }
}

function _proxy (data) {
  Object.keys(data).forEach(key => {
      Object.defineProperty(this, key, {
          configurable: true,
          enumerable: true,
          get: () => {
              return this._data[key];
          },
          set: (val) => {
              this._data[key] = val;
          }
      })
  });
}

首先在 Vue constructor 階段加上 _proxy.call(this, options.data);

這邊用到了 Function.prototype.call,簡單來說,我們透過 call 讓 Vue 的 this 裡面有除了有 _data 外,還有我們要的 text,透過 call ,能夠串接物件上的建構子

而另外有個 _proxy 的 function ,一樣要使用 Object.defineProperty 才會有響應式

在 proxy 後面我們加上 console.log 把 this 印出來看看

可以發現多了 data 裡的值,這樣我們就能直接取得 data 裡的值

另外我們這邊因為是簡單實作,就直接把 _data 寫死,在 Vue 源碼裡會特別加一個 sourceKey 參數,有興趣的請看下方

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
}

export function proxy (target: Object, sourceKey: string, key: string) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

響應式的部分就先到這邊,都是一些簡單的示範,雖然簡單但還是不太好理解,會需要反覆的看,但也是蠻有趣的!

參考:

如何在 Vue 裡插入 component:Vue.extend 使用

情境

事情是這樣的,在用第三方 UI 框架時有時候會碰到這種狀況:我想用這個 component 但是他有些地方不太滿意或不是我要的,我想要改這個 component 某部分的 DOM,並且 append 另一個 component 進去,應該怎麼改?

直接以我這次碰到的實際例子好了,我想用 element UI 裡面的 Select,但是需要改 Select 的 suffix 樣式,也就是右邊下拉式選單那個 icon。

2019-02-11 12 54 39

觀察

首先觀察一下結構,在 html 裡是直接以 el-selectel-option 引入

<el-select v-model="value" placeholder="请选择">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value">
    </el-option>
</el-select>

文件上,沒有提供 suffix 這個位置的 slot 或 props 可以使用,也就是說這個下拉式 icon 是預設包在這個 component 裡面的,並且沒有提供其他方式給開發者修改。(像 ant design 這裡就有提供 props 方式可以修改)

innerHTML 取代 DOM

馬上想到的就是使用 innerHTML 來取代 DOM,將想要改變的 html 使用 innerHTML 插入我們所選取的節點中,可以看到 icon 的位置已經成功替換成我們寫的 html。

mounted() {
    let suffix = document.querySelector('.el-input__suffix');
    suffix.innerHTML = '<p>123</p>'
}

2019-02-11 1 17 36

插入另一個 component:Vue.extend

剛剛示範的是純 html,但是如果我想放入的是另一個 component 呢?

假設我有一個 component 如下:

Vue.component('test', {
  template: `<p>abc</p>`
})

如果將剛剛的 innerHTML 替換成使用這個 component

mounted() {
    let suffix = document.querySelector('.el-input__suffix');
    suffix.innerHTML = '<test></test>'
}

會發現 icon 位置呈現是空白的,這是因為 innerHTML 直接將我們寫的當成純 html 直接印在網頁上,所以用右鍵查看元素會看到 <test></test> 直接被印出,沒有透過 Vue 編譯成 網頁看得懂的 html 。

花了一些時間 google,因為一直找不到適合的關鍵字,後來看到這篇覺得應該是我要找的。

主要是透過 Vue.extend 這個方法。這個方法是除了初始的 Vue instance 之外,可以在另外建立一個 Vue instance,所以裡面傳送的 object �和建立初始的 Vue instance 類似,不ㄧ樣的是 data 是用 function,也可以直接傳送 Vue.component。

const test = Vue.component('test', {
  template: `<p>abc</p>`
})
...
mounted() {
    let suffix = document.querySelector('.el-input__suffix');
    suffix.innerHTML = '';
    const testComponent = Vue.extend(test);
    var instance = new testComponent();
    instance.$mount();
    suffix.appendChild(instance.$el)
}
  1. ㄧ樣先取得 suffix 節點,suffix.innerHTML = ''; 將原來的 DOM 清空
  2. Vue.extend 放入我們想要呈現的 component,如果是 SFC,可以透過 import 的方式 import test from 'test'; 引入
  3. 跟初始 Vue instance 一樣必須 new 方式建立,
  4. 建立完使用 $mount() 掛載方法,但是不填入掛載位置,再透過 appendChild 方法掛載,詳細解釋如下

If elementOrSelector argument is not provided, the template will be rendered as an off-document element, and you will have to use native DOM API to insert it into the document yourself.

這樣就完成了!
codepen demo

keydown, keypress, keyup 的差異

看到胡大寫的這篇 從 React 原始碼看 keyPress 與 keyDown 事件,讓我也想跟著研究一下 keydown, keypress, keyup 三者的差異,該文已經敘述的很詳細了,我這邊只是試著再整理一下

keydown

  • 按下任何按鍵時觸發,包含 ESC 、 ENTER 鍵等等。
  • 連續按著按鍵,會連續觸發事件。
  • 這時取 input 的值會是輸入前的值

key

按鍵所表現的值,會跟著輸入法改變。文字按鍵的值,例如按 q 就會得到 q,有分大小寫,如果輸入中文例如「你」則會根據注音而分別觸發不同的事件並印出「ㄋ」「ㄧ」「ˇ」。非文字按鍵的值,例如按 shift 鍵會得到「"shift"」,按空白鍵會得到「 " " 」,這裡有對應表

code

這個比較針對按鍵的自身的代碼,例如數字1的按鍵會得到「Digit1」,就算實際輸入的是 ! ,因為都是按同一個鍵,所以都是得到「Digit1」的結果。

charCode (deprecated)

只有 keypress 事件有作用,這邊不管按什麼都只會輸出 0

keyCode (deprecated)

得到鍵盤所對應到的數字代碼,例如輸入 aA 會得到 65shift 鍵是 16,注意如果是中文一率都輸出 229

keypress(deprecated)

The keypress event is fired when a key that produces a character value is pressed down.

  • 只有能產生字的時候觸發,就是 input 有值才觸發。
  • 輸入注音符號時不會被觸發(因為還沒產生字),其他輸入法不確定。
  • 連續按著按鍵,會連續觸發事件。
  • 這時取 input 的值會是輸入前的值。

key/code

皆同 keydown

charCode

The Unicode reference number of the key

會得到跟 Unicode 對應的代碼,大小寫不同

keyCode (deprecated)

原本以為跟 keydown 一樣,但 keydown 不分大小寫,例如輸入 aA 會得到 65,但 keypress 會區分大小寫,輸入 a 得到 65A 會得到 97

keyup

The keyup event is fired when a key is released.

  • 離開按鍵時觸發。
  • 這時取 input 的值會是輸入完的值。

key/code/keyCode

皆同 keydown

總結

  1. keydownkeyup 可以說是一對的,keydown 是輸入前,keyup 是輸入完觸發。
  2. 因為 keyup 是離開按鍵才觸發所以只會觸發一次

可以到 codepen 測試網址 玩玩看

[學習筆記] Firebase 初體驗

記錄一下第一次使用firebase
先說明一下我主要用到的是資料庫功能

開始

到firebase網站建立一個新專案,我選擇的是 Cloud Firestore
不過我還不知道 Cloud Firestore 和另外一個 Realtime database 的差異
直接到 Database 新增「集合」

因為firebase 用的是 NoSQL,跟我之前學的 MySQL的觀念不太ㄧ樣
這邊是 Collection -> Doc -> Data 的層級
不過還沒時間詳細研究,不太確定是否正確

2018-03-23 12 48 45

我新增了一個 Products 集合,文件是一筆一筆的商品資料

如何在 Vue 專案中使用

首先要安裝套件

npm install firebase --save

我自己建立了一隻檔放firebase的設定

db.js

import firebase from 'firebase';

// Initialize Firebase
const config = {
    apiKey: '...',
    authDomain: '...',
    databaseURL: '...',
    projectId: '...',
    storageBucket: '',
    messagingSenderId: '...'
};

firebase.initializeApp(config);
// Required for side-effects
require('firebase/firestore');
const db = firebase.firestore();

export default db;

config的部分在firebase的console控制台有取得的地方

取得 db 資料

在需要取得db資料的檔案引入 db.js,並

import db from '@/db';
const productsRef = db.collection('products');

用下面的function取值,是參考firebase的文件(google的文件都寫的超詳細)
Get Data with Cloud Firestore

let list = [];
productsRef.get().then((querySnapshot) => {
            querySnapshot.forEach((doc) => {
                list.push(doc.data());
            });
        });
return list;

好像主要就應用到這裡,之後要再研究怎麼把資料存到資料庫,還有試試看部署網站

[學習筆記] 用Vuex狀態管理來做購物車

使用 github/vuex 提供的範例

start

在專案底下跑 yarn add vuex

src 下建立一個資料夾 store,裡面新增一個 index.js (未來可依功能命名)
index.js 中來寫我們的Vuex

import Vuex from 'vuex'
import Vue from 'vue'

Vue.use(Vuex)

new Vuex.Store({
    state: { ... }
})

Vuex.Store 裡會定義我們要用的所有方法

State

首先, state 就像是 .vue檔裡的 data
原本的我們可能會在 vue 裡寫這樣來定義products

data() {
        return {
            products: []
        }
    },

有了 Vuex,就會記錄在 state 裡,vue

export default new Vuex.Store({
    state: {    // = data
        products: []
    },
...
})

那Vue裡要怎麼引用呢?
會寫在computed裡,因為是可能會更動的

computed: {
        products() {
            return store.state.products
        }
    },

Mutation

在Vuex中,想要變更資料不能直接用平常assign的方式,一定要透過 mutations 資料才會被更動
這樣的好處是我們可以很容易地去看到是什麼事件改變了資料

假設今天想要做 改變products值 這個動作
在mutations裡定義一個事件 setProducts ,這個事件很專注的就是只做一件事就是變更 products

mutations: {
        setProducts(state, products) {
            // update products
            state.products = products;
        }
    }

那在vue裡呢?
在我們資料會變動的地方,使用 store.commit 呼叫 mutations

created() {
        // 在DOM載入完成前 先取得資料
        shop.getProducts(products => {
            // this.products = products 舊的寫法
            store.commit('setProducts', products)
        })
    }

Reference

vueschool 課程影片
Vuex 官方文件

(未完)

如何建立本機資料庫

前言

之前公司的專案一直是連測試機的 api 和資料庫,但這樣測試機更動時要開發就很不方便
於是請後端幫忙,讓我的專案連本機的 api 和資料庫,我在旁邊看也順便稍微紀錄ㄧ下

確認

首先到官網下載 MySQL
我安裝的是 mysql-5.7.25-macos10.14-x86_64.dmg 這個
下載成功後,打開系統偏好設定就會出現 MySQL 的 icon
打開並按下 start MySQL server

建立資料庫連線

啟用後,打開資料庫管理工具,我使用的是 Sequel Pro
新建一個連線,需要輸入 host username password
Host 固定為 127.0.0.1,username 為 root,password 預設沒有所以不用填
連線成功後,就可以新建資料庫了

開啟 api server

接下來要讓 api 專案連到這個資料庫,就到 env 去設定

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=(你的資料庫名稱)
DB_USERNAME=root
DB_PASSWORD=

設定好後,在 local 開啟 api server

最後就是讓開發中的專案連剛剛開的 api server
就完成囉

參考

[Note] Canonical

目前公司的首頁 routing 是這樣:首頁有幾個不同語系的首頁,都是靜態的 html,像是 home.html、home_en.html 等等,直接輸入根目錄 / 時在 Nginx 會導到 /home.html ,其他的 route 才會進到 Vue app 的 route

但現在問題是,google 搜尋時卻出現 /home_en.html 的搜尋結果

使用 google search console 發現,原來 home.html 沒有被收錄成標準網址,原因顯示為「重複網頁」,有可能是因為 /home.html 的內容是ㄧ樣所以被認定為重複網址

總之,現在要解決的就是要讓 /home.html 能被收錄成標準網址,目前我們的做法是先把根目錄 / 的 domain 從 google search console 砍掉,目前還不知道結果。

what is Canonical?

主要是避免重複的網頁內容。

例如,今天有一個網址 aaa.com ,然後他有個手機版網址 m.aaa.com ,由於內容是重複的,所以需要訂一個標準網址,一般來說會是桌機版使用 link rel="alternate" 指向手機版,手機版使用 link rel="canonical" 指向桌機版。

還有像是商品頁面網址帶不同參數時要顯示不同規格的照片、同樣的文章發佈到不同平台等等。

你可能會想說,那我不設標準網址會怎樣?不設的會就是 google 自行決定要出現哪個網址ㄛ

Reference

20190214

「雲端主機租賃及部署 Node.js 服務」
https://www.youtube.com/watch?v=2V6-ExuE0ns
文件:https://paper.dropbox.com/doc/T6dUTi5nEdafPnmOgyTt3

六角學院釋出他們線上問答的影片,主講者為卡斯伯,主講內容涵蓋雲端服務、虛擬主機、VPS 專用伺服器,

「使用 Node.js 開發 Discord bots 初學者課程」
https://www.udemy.com/discord-bots-development-in-nodejs-for-beginners/?ranMID=39197&ranEAID=BoHFIyu6APU&ranSiteID=BoHFIyu6APU-2AlERKqyNjAvH0jhmC9Ssw&LSNPUBID=BoHFIyu6APU

4小時 Discord bots 的 Udemy 課程,目前免費中

Discord bot https://discordbots.org/
是一個像 skype或 RC 的語音軟體

「uuid 產生器」
https://www.npmjs.com/package/uuid

還在煩惱怎麼產 id 嗎?
透過 uuid 產生器可以直接幫你產 id
共有五種版本 https://zh.wikipedia.org/wiki/%E9%80%9A%E7%94%A8%E5%94%AF%E4%B8%80%E8%AF%86%E5%88%AB%E7%A0%81

「json placeholder」
https://jsonplaceholder.typicode.com/

是一個免費提供的 RESTful 服務平台

如何在vue專案中引入reset.css

最近用Vue+webpack做了一個project
不過遇到一個小問題,就是我需要reset.css
公司的專案雖然也是用Vue,但沒參與到配置專案的部分,只知道reset這部分因為引入bootstrap而幫我們解決了,現在自己要引入reset.css還突然真不知道怎麼下手

看了一下專案的結構都沒有放css的地方,畢竟是vue嘛,每個component需要的css都寫在自己的.vue檔裡了
所以到底要放哪?

有看到npm有reset-css
本來懷疑用js的方式引入�不知道可不可行,原來94這麼簡單

首先當然要install它

$ npm install reset-css --save

找到你webpack引入(entry)的那隻js(我的是main.js),給他import進去就好了!

import 'reset-css/reset.css';

好像也有人這樣用

entry: {
    app: './src/main.js',

    'styles': [
      './node_modules/bootstrap/dist/css/bootstrap.min.css',
      './src/assets/css/design.css'
    ]
  },

將 Vue 元件庫打包上 npm

專案建置

使用 Vue cli3 建立專案

vue create [library]

檔案結構

dist
src/
    components/
        button/
            index.vue
            index.js // component 自己的 install
        checkbox/
            index.vue
            index.js // component 自己的 install
        ...
        index.js // 所有 component 的 install
package.json

dist 資料夾是每次跑 build 後自動建置的檔案
components 裡就是各種元件,index.vue 是元件本身,index.js 是寫元件本身的 install

專案使用套件時有分完整引入及按需引入,如果你不想要元件可以按需引入的話就不需要

元件建置

就跟平常寫 component 時一樣,這邊就不詳細描述

component/button.vue

template(lang="pug")
    button(:class="exportClass")
        slot

撰寫 install 安裝檔

component 開發完後,就是要寫安裝檔,這邊有分 所有元件個別元件 的安裝檔

所有元件載入

src/components/index.js

// 載入所有元件
import BaseButton from './BaseButton/index.vue'
import BaseCheckbox from './BaseCheckbox/index.vue'
import BaseInput from './BaseInput/index.vue'
...

const Components = [
    BaseButton, BaseCheckbox, BaseInput...
]

const install = function (Vue, opts = {}) {
  Components.forEach(component => {
    Vue.component(component.name, component)
  })
}

export default {
  install
}

install 的 function 會去跑所有 component,並透過 Vue.component 完成載入

這樣到時候就是整包載入,當呼叫 Vue.use 時就是呼叫上面的 install

import yourLibrary from 'your-library'

Vue.use(yourLibrary)

個別元件載入

個別元件要能達成就是每個元件都有自己的 install
在每個 component 的資料夾下多了一個 index.js 檔

src/components/BaseButton/index.js

import BaseButton from './index.vue'

/* istanbul ignore next */
BaseButton.install = function (Vue) {
  Vue.component(BaseButton.name, BaseButton)
}

export default BaseButton

這邊的方式是,將每個 component 都加上 install 的 function

src/components/index.js

// 載入所有元件
import BaseButton from './BaseButton/index.js'
import BaseCheckbox from './BaseCheckbox/index.js'
import BaseInput from './BaseInput/index.js'
...

const Components = [
    BaseButton, BaseCheckbox, BaseInput...
]

const install = function (Vue, opts = {}) {
  Components.forEach(component => {
    Vue.component(component.name, component)
  })
}

export default {
  install,
  BaseButton,
  BaseCheckbox,
  BaseInput
}

這支也要稍微更動,將所有 component 改成引入 index.js
最後 export 也要把所有 component 釋出

這邊的 install 拿掉的話,就不能全部載入了

import yourLibrary from 'your-library'

Vue.use(yourLibrary.BaseButton)
Vue.use(yourLibrary.BaseCheckbox)
...

build 指令

接下來就可以準備打包檔案了

Vue-cli 有提供 Library 的建置指令

vue-cli-service build --target lib --name myLib [entry]

把它放進 package.json 裡

package.json

{
    "scripts": {
        "build": "vue-cli-service build --target lib --name [your-library] ./src/components/index.js"
    },
    ...
}

就可以透過指令完成打包

yarn build

建完可以發現 dist 多了幾個檔案

File                                 Size               Gzipped

  dist/[your-library].umd.min.js    45.80 KiB          15.25 KiB
  dist/[your-library].umd.js        138.34 KiB         28.92 KiB
  dist/[your-library].common.js     137.94 KiB         28.80 KiB
  dist/[your-library].css           3.91 KiB           1.09 KiB

設定 package.json

{
  "name": "[your-library]", 
  "version": "1.0.0",
  "main": "dist/[your-library].common.js", 
  "scripts": {
    ...
    "build": "vue-cli-service build --target lib --name [your-library] ./src/components/index.js" 
  }
}

主要是設定發佈到 npm 上的一些設定

必填的有 name (npm package 的名稱)
version(版本號)每次發佈都要大於過去的版本
main(主要的檔案入口)就是我們打包後的檔案

private 不能設為 true(除非有升級 npm 帳戶)

其他欄位可以參考 creating a package json file

npm 發佈

要先在 npm 申請帳號,email 要經過驗證

npm adduser
npm login

一鍵發佈

npm publish

發佈後就能在 npm 找到並載入

使用

這樣就能開始使用

全部引入

import yourLibrary from 'your-library'

Vue.use(yourLibrary)

部分引入

import yourLibrary from 'your-library'

Vue.use(yourLibrary.BaseButton)
Vue.use(yourLibrary.BaseCheckbox)
...

參考資料

[第一週] Command Line 超新手入門

Command Line 是什麼?

操作電腦有兩種

  1. Graphical User Interface(GUI) 圖形化使用者介面
  2. Command Line Interface(CLI) 命令列介面

Command Line 工具

windows: 下載 cmder

mac: 原生有 terminal.app,可另外下載 iTerm2(可搭配 oh-my-zsh 換樣式)

常用指令

  • pwd: print working directory

印出目前資料夾的路徑

$ pwd
Users/debbyji/Project/mentor-program-3rd-jijigo
  • ls: list

列出資料夾下的所有檔案

-al 印出詳細的資訊及隱藏檔

$ ls
README.md         homeworks         package-lock.json
codewar.md        node_modules      package.json

$ ls -al
total 352
drwxr-xr-x   11 debbyji  staff     352 Apr 28 19:06 .
drwxr-xr-x   38 debbyji  staff    1216 Sep  4 22:11 ..
-rw-r--r--    1 debbyji  staff     362 Apr 28 19:06 .eslintrc.js
drwxr-xr-x   14 debbyji  staff     448 Oct  5 21:04 .git
-rw-r--r--    1 debbyji  staff      32 Apr 28 19:06 .gitignore
-rw-r--r--    1 debbyji  staff   42473 Apr 28 19:06 README.md
-rw-r--r--    1 debbyji  staff    5535 Apr 28 19:06 codewar.md
drwxr-xr-x   26 debbyji  staff     832 Apr 24 23:56 homeworks
drwxr-xr-x  314 debbyji  staff   10048 Apr 28 19:05 node_modules
-rw-r--r--    1 debbyji  staff  113215 Apr 28 19:06 package-lock.json
-rw-r--r--    1 debbyji  staff     940 Apr 28 19:06 package.json
  • cd: Change Directory

切換資料夾

cd test // 切到目前資料夾下的 test 資料夾

cd .. // 切到上一層資料夾

cd ~ // 切到 user 目錄下
  • man: Manal

指令使用手冊,會列出該指令所有可使用的參數

man ls
  • touch

更改檔案時間(檔案存在時)或建立檔案(檔案不存在時)

touch test.js
  • rm: Remove

刪除檔案(file)

rm test.js

如果要刪除資料夾有下面兩種

rmdir test
// or
rm -r test

-r: 刪除咨諒夾擊下面所有檔案
-f: 強制刪除的意思

  • mkdir: Make Directory

建立資料夾

mkdir testfolder
  • mv: Move

移動檔案或改名

移動 test.js 至 testfolder 資料夾內

mv test.js testfolder

將 test.js 改名為 hello.js

mv test.js hello.js

補充:路徑最前面如果有 / 就是絕對路徑,如果沒有則是相對路徑

/user/debby.. (絕對路徑)

user/debby (相對路徑)
  • cp: Copy

複製檔案

cp test.js test2.js

複製資料夾(如同前面的 rm)

cp -r test test2

vim

一個文字編輯器

選擇要編輯的檔案

vim test.js

一進去後會發現不能打字

如何打字:

i 進入 Insert 模式,再按 ESC 離開 Insert 模式

如何離開 vim:

  • :q Enter 即可離開
  • :wq 存檔離開

補充

cat 可以快速的印出檔案的內容(針對檔案內容不多的且只想快速地看一下的,好用)

cat test

其他好用的指令

grep:抓取關鍵字、搜尋很常用

想在 test.js file 中找到有 j 的那行

grep j test.js

wget:下載檔案

想透過圖片網址來下載圖片,用 open 檔名 來開啟

wget https://avatars1.githubusercontent.com/u/2755720\?v\=4
open 2755720\?v\=4

也可以下載網頁原始碼

wget www.google.com

curl:送出 request、測試 API

-I 可以看到完整 response

curl https://jsonplaceholder.typicode.com/todos/1

curl -I https://jsonplaceholder.typicode.com/todos/1

redirect:重新導向 input/output

使用 > 符號
想將 ls -al 的 output 原本直接印出在 command line,想要改存在某個檔案裡

ls -al > list_result

我們也可以把 echo 的東西存到檔案中

echo 123 > 123.txt

不過 > 符號每次都會覆蓋掉檔案所有內容
如果只想新增(append)的話使用 >>

echo "append to the end of the file" >> 123.txt

補充: echo 是直接在 command line 印出 echo 後方的內容

echo 123

echo "123 " // 有空格就需要雙引號

pipe:指令

pipe 就是 | 符號,他可以將符號左邊的輸出變成符號右邊的輸入

例如:我想要印出 123.txt 同時搜尋 hello 這個關鍵字

cat 123.txt | grep hello

這其實跟下面的程式一樣

grep hello 123.txt

pipe 還可以接像 sort uniq 等等

此為 程式導師實驗計畫第三期 裡的「[CMD101] Command Line 超新手入門:全部」筆記

[學習筆記] Redux

延續上一篇 react-app,直接改寫成加入 redux 的版本

安裝所需套件

npm install --save redux react-redux redux-thunk

連結 React 和 Redux

要讓 React 和 Redux 連結有兩種方式
第一種是 connect()
第二種是 provider

Provider 把整個 App 包起來

App.js

import { Provider } from 'react-redux'
...
<Router basename={process.env.PUBLIC_URL}>
        <Provider store={store}>
          <div className="App">
            <Header />
            <Route exact path="/" render={props => (
              <React.Fragment>
                <AddTodo addTodo={this.addTodo}/>
                <TodoList todos={this.state.todos} markComplete={this.markComplete}
                  deleteTodo={this.deleteTodo} />
              </React.Fragment>
            )} />
            <Route path="/about" component={About} />
          </div>
        </Provider>
      </Router>

reducer

建立 reducers/index.js

這邊 combind 所有 reducers

import { combineReducers } from 'redux';
import todoReducer from './todoReducer';

export default combineReducers({
  addTodo: todoReducer
})

reducers/todoReducer.js

import { FETCH_TODOS, ADD_TODO } from '../actions/types';

const initialState = {
  todos: []
}

// state, action
export default function(state = initialState, action) {
  switch (action.type) {
    default:
      return state
  }
}

action

action/type.js

export const FETCH_TODOS = 'FETCH_TODOS';
export const ADD_TODOS = 'ADD_TODOS';

action/todoAction.js

import { FETCH_TODOS, ADD_TODO } from '../actions/types';

export function fetchTodos () {
  return function(dispatch) {
    Axios.get('https://jsonplaceholder.typicode.com/todos?_limit=10')
    .then(todos => dispatch({
      type: FETCH_TODOS,
      payload: todos.data
    }))
  }
}

改成 arrow function

export const fetchTodos = () => dispatch => {
    Axios.get('https://jsonplaceholder.typicode.com/todos?_limit=10')
      .then(todos => dispatch({
        type: FETCH_TODOS,
        payload: todos.data
      }))
}

回到 reducer,新增一個 case

action/todoActions.js

export default function(state = initialState, action) {
  switch (action.type) {
    case FETCH_TODOS: {
      return {
        ...state,
        todos: action.payload
      }
    }
    default:
      return state
  }
}

跟 component 結合
用到 connect()

components/todoList.js

import { connect } from 'react-redux';
import { fetchTodos } from '../actions/todoActions';

componentWillMount() {
        this.props.fetchTodos();
    }

...
export default connect(null, {fetchTodos})(TodoList);

componentWillMount 這個階段呼叫 fetchTodos
注意的是,這裡的方式是用 props 取得

但是 store 裡的 state 如果要和 component 結合
就要使用 mapState

const mapStateToProps = state => {
    return {
        todos: state.todos.todos
    }
}
...
export default connect(mapStateToProps, {fetchTodos})(TodoList);

補上 props type

TodoList.propTypes = {
    fetchTodos: PropTypes.func.isRequired,
    todos: PropTypes.Array.isRequired
}

add redux devtool

Redux devtool
https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd

安裝後會發現 redux 只出現 No store found. Make sure to follow the instructions.

這時要稍微修改一下 store.js

import { createStore, applyMiddleware, compose } from 'redux'
...
const store = createStore(
  rootReducer,
  initialState,
  compose(
    applyMiddleware(...middleware),
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  ));

增加了 compose

SOP

  1. add new action type

先新增一個 type

export const ADD_TODO = 'ADD_TODO';
  1. add new action

todoAction.js

export const addTodo = (title) => dispatch => {
  Axios.post('https://jsonplaceholder.typicode.com/todos', {
      title,
      completed: false
    })
    .then(todos => dispatch({
      type: ADD_TODO,
      payload: todos.data
    }))
}
  1. add reducer
case ADD_TODO: {
      return {
        todos: [
          ...state.todos,
          action.payload
        ]
      }
    }
  1. connect

在要執行 function 的檔案

import { connect } from 'react-redux';
import { addTodo } from '../actions/todoActions';
...
onSubmit = (e) => {
    e.preventDefault();
    this.props.addTodo(this.state.title);
  }
...
export default connect(null, { addTodo })(AddTodo);
  1. add props-type
// propsType
AddTodo.propTypes = {
  addTodo: PropTypes.func.isRequired
}

reference

一日圖書館管理員:HTTP Challenge

@huli 大大開發的小遊戲,因為覺得概念實在太有趣了,就來玩玩看並做個簡單的紀錄。

主要會練習到 GET POST DELETE PATCH 這四種常見的方法,還有 query string 的運用,以及 Authoration 驗證。

除了 GET 方法會直接在網頁的 URL 輸入之外,其他的方法我會透過 Postman 來幫忙。

那麼就開始吧!遊戲網址

Lv0:熱身

https://lidemy-http-challenge.herokuapp.com/start

安安,歡迎來到 Lidemy HTTP challenge。

這裡一共有 10 道關卡,每一關的網址皆為 /lvX,X 即為關卡的數字。
但只知道網址是沒有用的,需要搭配正確的 token 才能順利進入關卡,傳入 token 的方式為 query string,例如 /lv1?token=xxx。

另外,為了讓你方便辨別這是 token,token 的內容一定是用一個大括弧刮起來的字串。
每一關都會有提示,只要按照提示照著做,就能拿到前往下一關的 token。

準備好了嗎?

第一關的 token 為:{GOGOGO}

給第一次進來的朋友的遊戲解說。

Lv1:GET

https://lidemy-http-challenge.herokuapp.com/lv1?token={GOGOGO}

啊...好久沒有看到年輕人到我這個圖書館了,我叫做 lib,是這個圖書館的管理員。
很開心看到有年輕人願意來幫忙,最近圖書館剛換了資訊系統,我都搞不清楚怎麼用了...這是他們提供給我的文件,我一個字都看不懂,但對你可能會有幫助:https://gist.github.com/aszx87410/3873b3d9cbb28cb6fcbb85bf493b63ba

你叫做什麼名字呢?用 GET 方法跟我說你的 name 叫做什麼吧!

直接在 query string 上加上 name={你的名字}

https://lidemy-http-challenge.herokuapp.com/lv1?token={GOGOGO}&name=Debby

啊...原來你叫 Debby 啊!下一關的 token 是 {HellOWOrld}

Lv2:還是 GET

https://lidemy-http-challenge.herokuapp.com/lv2?token={HellOWOrld}

我前陣子在整理書籍的時候看到了一本我很喜歡的書,可是現在卻怎麼想都想不起來是哪一本...
我只記得那本書的 id 是兩位數,介於 54~58 之間,你可以幫幫我嗎?
找到是哪一本之後把書的 id 用 GET 傳給我就行了。

一樣是 GET,只要在 query string 加上 id,後面的數字就是 54~58 選一個,試到對為止。

如果不對的話,會回應 好像不是這本書耶...,最後試出來是 56。

https://lidemy-http-challenge.herokuapp.com/lv2?token={HellOWOrld}&id=56

啊!就是這本書,太謝謝你了。下一關的 token 為:{5566NO1}

5566NO1... 作者私心?

Lv3:POST

https://lidemy-http-challenge.herokuapp.com/lv3?token={5566NO1}

真是太感謝你幫我找到這本書了!

剛剛在你找書的時候有一批新的書籍送來了,是這次圖書館根據讀者的推薦買的新書,其中有一本我特別喜歡,想要優先上架。
書名是《大腦喜歡這樣學》,ISBN 為 9789863594475。

就拜託你了。
新增完之後幫我把書籍的 id 用 GET 告訴我。

POST 的部分就請 Postman 來幫忙,參考一下 圖書館資訊系統 API 文件

POST 的 url 是這樣:https://lidemy-http-challenge.herokuapp.com/books
Body 的格式選擇 x-www-form-urlencoded ,再把 token name ISBN 填入,按下 send

2019-03-05 10 36 43

{"message":"新增成功","id":"1989"}

把 id 用 queryString 帶入

https://lidemy-http-challenge.herokuapp.com/lv3?token={5566NO1}&name=Debby&id=1989

這樣子讀者就能趕快看到這本新書了,謝謝!下一關的 token 為 {LEarnHOWtoLeArn}

Lv4:GET 的 query 練習

https://lidemy-http-challenge.herokuapp.com/lv4?token={LEarnHOWtoLeArn}

我翻了一下你之前幫我找的那本書,發現我記錯了...這不是我朝思暮想的那一本。
我之前跟你講的線索好像都是錯的,我記到別本書去了,真是抱歉啊。
我記得我想找的那本書,書名有:「世界」兩字,而且是村上春樹寫的,可以幫我找到書的 id 並傳給我嗎?

這是 GET 方法。

其中提到書名有「世界」兩字,根據 API 文件,使用參數 q 來查詢。
而雖然題目有提到作者是要「村上春樹」,但 API 並沒有提供篩選作者的方法,所以只能靠肉眼找出 id(試過 ?q=世界&author=村上春樹,結果相同)

https://lidemy-http-challenge.herokuapp.com/books?q=世界

[{"id":2,"name":"當我想你時,全世界都救不了我","author":"肆一","ISBN":"5549173495"},{"id":27,"name":"從你的全世界路過","author":"張嘉佳","ISBN":"8426216529"},{"id":79,"name":"世界末日與冷酷異境","author":"村上春樹","ISBN":"9571313408"},{"id":90,"name":"文學的40堂公開課:從神話到當代暢銷書,文學如何影響我們、帶領我們理解這個世界","author":"約翰.薩德蘭","ISBN":"7978376866"}]

找到 id 為 79

https://lidemy-http-challenge.herokuapp.com/lv4?token={LEarnHOWtoLeArn}&id=79

沒錯!這次我很確定了,就是這本!下一關的 token 為 {HarukiMurakami}

Lv5:delete 練習

https://lidemy-http-challenge.herokuapp.com/lv5?token={HarukiMurakami}

昨天有個人匆匆忙忙跑過來說他不小心捐錯書了,想要來問可不可以把書拿回去。
跟他溝通過後,我就把他捐過來的書還他了,所以現在要把這本書從系統裡面刪掉才行。

那本書的 id 是 23,你可以幫我刪掉嗎?

這次使用 DELETE 方法,

Lv6:Authorization 驗證

https://lidemy-http-challenge.herokuapp.com/lv6?token={CHICKENCUTLET}

我終於知道上次哪裡怪怪的了!

照理來說要進入系統應該要先登入才對,怎麼沒有登入就可以新增刪除...
這太奇怪了,我已經回報給那邊的工程師了,他們給了我一份新的文件:https://gist.github.com/aszx87410/1e5e5105c1c35197f55c485a88b0328a。

這邊是帳號密碼,你先登入試試看吧,可以呼叫一個 /me 的 endpoint,裡面會給你一個 email。
把 email 放在 query string 上面帶過來,我看看是不是對的。

帳號:admin
密碼:admin123

這次多了一個新版的 API文件

如果我直接 GET https://lidemy-http-challenge.herokuapp.com/v2/me ,會給我一個 Invalid Authorization token 的錯誤

在 Postman 上,Authorization 選擇 Basic Auth,username 和 password 就填上題目寫的帳號跟密碼

將 email 放在 queryString

https://lidemy-http-challenge.herokuapp.com/lv6?token={CHICKENCUTLET}&[email protected]

對對對,就是這個,這樣才對嘛!下一關的 token 為 {SECurityIsImPORTant}

Lv7:Authorization 版的 DELETE

https://lidemy-http-challenge.herokuapp.com/lv7?token={SECurityIsImPORTant}

那邊的工程師說系統整個修復完成了,剛好昨天我們發現有一本書被偷走了...
這本書我們已經買第五次了,每次都被偷走,看來這本書很熱門啊。
我們要把這本書從系統裡面刪掉,就拜託你了。

對了!記得要用新的系統喔,舊的已經完全廢棄不用了。

書的 id 是 89。

一樣是 DELETE 方法,不同的是,這次要加上剛剛的 Authorization

Lv8:PATCH 更新

https://lidemy-http-challenge.herokuapp.com/lv8?token={HsifnAerok}

我昨天在整理書籍的時候發現有一本書的 ISBN 編號跟系統內的對不上,仔細看了一下發現我當時輸入系統時 key 錯了。
哎呀,人老了就是這樣,老是會看錯。

那本書的名字裡面有個「我」,作者的名字是四個字,key 錯的 ISBN 最後一碼為 7,只要把最後一碼改成 3 就行了。
對了!記得要用新的系統喔,舊的已經完全廢棄不用了。

首先,先找到那本書,使用 GET

https://lidemy-http-challenge.herokuapp.com/v2/books?q=我

[{"id":2,"name":"當我想你時,全世界都救不了我","author":"肆一","ISBN":"5549173495"},{"id":3,"name":"我殺的人與殺我的人","author":"東山彰良","ISBN":"9262228645"},{"id":7,"name":"你已走遠,我還在練習道別","author":"渺渺","ISBN":"3722233689"},{"id":9,"name":"你是我最熟悉的陌生人","author":"Middle","ISBN":"9765734253"},{"id":22,"name":"我輩中人:寫給中年人的情書","author":"張曼娟","ISBN":"7241428897"},{"id":38,"name":"我和我追逐的垃圾車","author":"謝子凡","ISBN":"7797349452"},{"id":57,"name":"我的櫻花戀人","author":"宇山佳佑","ISBN":"2947749939"},{"id":60,"name":"你走慢了我的時間","author":"張西","ISBN":"8811544334"},{"id":66,"name":"我是許涼涼","author":"李維菁","ISBN":"8389193464"},{"id":72,"name":"日日好日:茶道教我的幸福15味【電影書腰版】","author":"森下典子","ISBN":"9981835427"},{"id":90,"name":"文學的40堂公開課:從神話到當代暢銷書,文學如何影響我們、帶領我們理解這個世界","author":"約翰.薩德蘭","ISBN":"7978376866"},{"id":95,"name":"我想吃掉你的胰臟【電影珍藏版】","author":"住野夜","ISBN":"2615985356"},{"id":100,"name":"慢情書:我們會在更好的地方相遇嗎?","author":"林達陽","ISBN":"7418527246"}]

根據題目描述,作者的名字是四個字、ISBN 最後一碼為 7 的應該是這本:

{"id":72,"name":"日日好日:茶道教我的幸福15味【電影書腰版】","author":"森下典子","ISBN":"9981835427"}

接著使用 PATCH,因為更新也要傳遞資料,所以方法類似於 POST 會需要帶資料,而要帶的資料就是想要更新的資料,例如這個題目就是將 ISBN 參數帶上欲更改的

Lv9:特殊的 Header

https://lidemy-http-challenge.herokuapp.com/lv9?token={NeuN}

API 文件裡面有個獲取系統資訊的 endpoint 你記得嗎?
工程師跟我說這個網址不太一樣,用一般的方法是沒辦法成功拿到回傳值的。

想要存取的話要符合兩個條件:
1. 帶上一個 X-Library-Number 的 header,我們圖書館的編號是 20
2. 伺服器會用 user agent 檢查是否是從 Safari 送出的Request,不是的話會擋掉

順利拿到系統資訊之後應該會有個叫做 version 的欄位,把裡面的值放在 query syring 給我吧。

用一般的方法是沒辦法成功拿到回傳值的。

我們先試試看用一般的方法會怎樣,很單純的使用 GET 與 Authorization:

GET /v2/sys_info HTTP/1.1
Host: lidemy-http-challenge.herokuapp.com
Authorization: Basic YWRtaW46YWRtaW4xMjM=
Cache-Control: no-cache

會得到 Invalid Library Number 這樣的錯誤。

根據題目,我們在 header 加上 X-Library-Numberuser-agent

得到 version 了!

https://lidemy-http-challenge.herokuapp.com/lv9?token={NeuN}&version=1A4938Jl7

限制這麼多都被你突破了,真有你的。要不要考慮以後來我們圖書館當工程師啊?下一關的 token 為 {duZDsG3tvoA}

Lv10:最終關-猜數字

https://lidemy-http-challenge.herokuapp.com/lv10?token={duZDsG3tvoA}

時間過得真快啊,今天是你在這邊幫忙的最後一天了。

我們來玩個遊戲吧?你有玩過猜數字嗎?

出題者會出一個四位數不重複的數字,例如說 9487。
你如果猜 9876,我會跟你說 1A2B,1A 代表 9 位置對數字也對,2B 代表 8 跟 7 你猜對了但位置錯了。

開始吧,把你要猜的數字放在 query string 用 num 當作 key 傳給我。

居然是幾A幾B當結尾!那這就不破梗了,讓觀眾自己猜。
不過還是要放張過關圖🎉

script 中的 defer 和 async

前端面試的必考題之一

來源:https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

<script src="http://libs.baidu.com/jquery/2.0.0/jquery.js"></script>

一般的 script tag

瀏覽器照的 html 一行一行的解析(parsing),當遇到 script tag 時,解析就會暫停,並開始下載 script 檔,下載完直接執行,執行完再繼續解析 html。

缺點:如果該 script 放在 html 的 head,加上 script 本身檔案大的話,很可能因為下載太久造成一段時間畫面空白。

async

ㄧ樣 html 解析,當遇到 script tag 時,會在背景下載 script 檔,而解析不會暫停,不過下載完就會直接執行,此時解析就會暫停,等 scirpt 執行完。

缺點:當有很多 script 檔時,不能決定 script 執行的順序,因為下載完就會直接執行,所以順序是看檔案下載速度的快慢,下載快的就先執行。

適合沒有依賴其他 script 的檔案或是較小的的檔案,而因為執行時可能 html 還沒解析完,所以也適合不用對 DOM 動作的檔案,像是 gtm 、 google analysis、 google map等。

defer

當遇到 script tag 時,跟 async 一樣會先在背景下載,不ㄧ樣的是下載完不會馬上執行,而是等 html 解析完才執行。

適合有其他依賴檔案才能執行的,或是會對 DOM 動作的檔案。

其他

  • 要注意的是,async 和 defer 只有在有 src attribute 的時候才有用,所以 inline scirpt 是沒用的
  • async defer 同時會怎樣?

例如常見的 google map api

<script src="https://maps.googleapis.com/maps/api/js" async defer></script>

因為 async 在有些較舊的瀏覽器是不能用的,而 defer 可以支援那些較舊的瀏覽器,當兩個都支援時,async 的優先度較高,此時 defer 會被忽略

參考來源

JavaScript100days#2 - 資料型別

Javascript 的資料型別

原始型態(primitive type)

  1. null
  2. undefined
  3. string
  4. number
  5. boolean
  6. symbol(es6)

物件型態(object type)

  1. object(包含array, object, function, date)

typeof 檢查型別

注意回傳的是字串!

typeof null // "object"
typeof undefined // "undefined"
typeof 'abc' // "string"
typeof 123 // "number"
typeof function(){} // "function" 
typeod [] // "object"

例外:

typeof null // object 

這是最廣為人知的一個 bug,參考來源

如何判斷陣列

由於陣列使用 typeof 會等於 object,可以使用 Array.isArray 來判斷。

Array.isArray([]) //true

如何判斷 NaN

其實 NaN 是不代表任何數字,但使用 typeof 會等於 number,可以使用 isNaN 來判斷。

isNaN(NaN) // true

更精確的判斷型別

可以用 object.prototype.toString.call() 來判斷

Object.prototype.toString.call(null); // [object null]
var date = new Date()
Object.prototype.toString.call(date); // [object date]

用 typeof 的優點

當值沒有被宣告時也不會報錯,適合用來檢測值是否存在

if (type a !== 'undefined') {
  // do something
}
// 如果沒宣告也不會報錯

if (a !== 'undefined') {
  // do something
}
// 出現 Uncaught ReferenceError: a is not defined

總結

  • 了解 JavaScript 的資料型態有哪些,基本型態和物件型態有什麼差別
  • 用 typeof 檢查型別有哪些例外

dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.58.dylib 問題

昨天因為在用React的環境,隔天上班一如往常準備下 php artisan serve 來開啟專案serve
突然出現error

dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.58.dylib
Referenced from: /usr/local/bin/node
Reason: image not found 

發現是php不見了,
參考了 這篇 的解決方法
brew reinstall [email protected] (因為我們公司用7.2)
終於php回來了

後來,要build專案時,又出錯了

ERROR in ./resources/assets/sass/app.scss
Module build failed: ModuleBuildError: Module build failed: Error: Missing binding /Applications/AMPPS/www/sharent/web/node_modules/node-sass/vendor/darwin-x64-67/binding.node
Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 11.x

原來是因為我npm版本升級後,node-sass 也要安裝到最新版
根據它的指令 npm rebuild node-sass 重裝即可

JavaScript100days#1 - Array 的各種方法(上)

array 有分三種類型的方法

  1. Mutator methods: setter 會更動原來的陣列
  2. Accessor methods: getter 不會更動原來陣列,作存取的動作
  3. Iteration methods 迭代

Mutator methods

會更動原來的陣列

push

將值插入原陣列的最後
return 新陣列的長度

var ary = [1, 2, 3, 4, 5];
ary.push(6) // return 6
console.log(ary) // [1, 2, 3, 4, 5, 6]

pop

移除原陣列最後一個值
return 移除掉的值

var ary = ["A", "B", "C", "D", "E"]
ary.pop(); // return "E"
console.log(ary); // ["A", "B", "C", "D"]

reverse

將原陣列的排序顛倒
return 顛倒後的陣列

var ary = ["A", "B", "C", "D", "E"]
ary.reverse() // return ["E", "D", "C", "B", "A"]
console.log(ary.reverse()) // ["E", "D", "C", "B", "A"]

shift

移除陣列的第一個值
return 被移除的值

var ary = ["A", "B", "C", "D", "E"]
ary.shift() // retrun "A"
console.log(ary) // ["B", "C", "D", "E"]

unshift

把值插入在陣列的第一個位置
return 新陣列的長度

var ary = ["A", "B", "C", "D", "E"]
ary.unshift("F") // retrun 6
console.log(ary) // ["F", "A", "B", "C", "D", "E"]

splice

  1. 移除
  2. 加入

splice(startIndex, deleteCount, newValue)

共有三個參數,第一個為起始點,第二個欲移除的數量(長度),第三個是插入的值二三都是非必填。
如果第二個沒有填,則預設不限長度(就會從起始點到最後),如果輸入0,就沒有值會被移除。
return 被移除的值

var ary = ["A", "B", "C", "D", "E"]
splice(2, 0) // ["C", "D", "E"]
console.log(ary) // ["A", "B"]

var ary = ["A", "B", "C", "D", "E"]
splice(2, 1) // ["C"]
console.log(ary) // ["A", "B", "D", "E"]

var ary = ["A", "B", "C", "D", "E"]
ary.splice(2, 0, "F") // [] 因為沒有被移除的值
console.log(ary) // ["A", "B", "C", "F", D", "E"]

sort

做陣列的排序
return 排序後的陣列

第一種沒有 function 參數的,會轉成字串並用 UTF-16 來比對,數字也是會被轉成字串。

var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months); //  ["Dec", "Feb", "Jan", "March"]

var array1 = [1, 30, 4, 21, 100000];
array1.sort();
console.log(array1);
// expected output: Array [1, 100000, 21, 30, 4]

那要做數字的排序怎麼做?
就是代入一個比對的 function,這個 function 要帶入兩個參數(例如a, b),兩兩比對的方式,根據回傳值有三種結果:小於 0 為 a 小於 b(a 在前),大於 0 為 a 大於 b(b 在前),若為 0 則相等。
最常用來做數值排序的函數就是用 a - b 的方式,這是由小到大,如果要倒序(由大到小),可以改為 b - a

var numbers = [4, 2, 5, 1, 3];
numbers.sort(function(a, b) {
  return a - b;
});
console.log(numbers);

// [1, 2, 3, 4, 5]

Accessor methods

僅供存取的方法,原陣列不會被改變。

concat

合併陣列
return 合併後的陣列

var array1 = ['a', 'b', 'c'];
var array2 = ['d', 'e', 'f'];

console.log(array1.concat(array2));
// expected output: Array ["a", "b", "c", "d", "e", "f"]

要注意的是,如果陣列裡有包含 object,是複製 object 的 reference。

var obj = {
    name: 'john'
}
var a = [obj].concat("abc")
a[0].name = "mary"
console.log(obj.name) // mary

includes

arr.includes(valueToFind[, fromIndex])

查詢某個值是否在這個陣列中
return Boolean 值

ps. 使用 Same-value-zero 演算法

var array1 = [1, 2, 3];

console.log(array1.includes(2));
// expected output: true

var pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat'));
// expected output: true

如果值是字串,大小寫是不同的

第二個參數 fromIndex 是搜尋的起始的索引位置,預設是 0,可以接受負數(真正的起始位置可以這樣算 array.length + fromIndex

var arr = ['a', 'b', 'c'];

arr.includes('a', -100); // true
arr.includes('b', -100); // true
arr.includes('c', -100); // true
arr.includes('a', -2); // false

indexOf

查詢某個值是否存在;某個值在陣列的第幾個位置
return index值,如果不存在則回傳 -1

var beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];

console.log(beasts.indexOf('bison'));
// expected output: 1

// start from index 2
console.log(beasts.indexOf('bison', 2));
// expected output: 4

console.log(beasts.indexOf('giraffe'));
// expected output: -1

includes 一樣可代入第二個參數 fromIndex 決定要搜尋的起始位置

lastIndexOf

查詢某個值在陣列中最後出現的位置,也就是如果同樣的值出現在陣列好幾次,只會回傳最後出現的 index 位置。

return index值,如果不存在則回傳 -1

ㄧ樣可以代入第二個參數 fromIndex 決定要搜尋的起始位置,如果要回傳第一個出現的值,就是用 indexof

var numbers = [2, 5, 9, 2];
numbers.lastIndexOf(2);     // 3
numbers.lastIndexOf(7);     // -1
numbers.lastIndexOf(2, 3);  // 3
numbers.lastIndexOf(2, 2);  // 0
numbers.lastIndexOf(2, -2); // 0
numbers.lastIndexOf(2, -1); // 3
numbers.indexOf(2) // 0 回傳第一個

join

將陣列合併並轉成字串
如果不代入參數,則會預設用逗號 , 合併
return 合併後的字串

var a = ['Wind', 'Rain', 'Fire'];
a.join();      // 'Wind,Rain,Fire'
a.join(', ');  // 'Wind, Rain, Fire'
a.join(' + '); // 'Wind + Rain + Fire'
a.join('');    // 'WindRainFire'

slice

arr.slice([begin[, end]])

擷取陣列中的部分值
第一個參數為起始位置,如果沒有第二個參數,則一路取到最後。
第二個參數為結束位置,注意是擷取到結束位置「之前」,結束位置上的值是不會被取到的!

var animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

console.log(animals.slice(2));
// expected output: Array ["camel", "duck", "elephant"]

console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]

console.log(animals.slice(1, 5));
// expected output: Array ["bison", "camel", "duck", "elephant"]

要注意的是,如果陣列裡有包含 object,是擷取 object 的 reference。

var ary = [{ name: 'john'}, '1', '2', '3'];
var ary2 = ary.slice(0, 2)
ary2.name = 'mary'
console.log(ary.name) // mary

[第一週] Git 超新手入門

Git 超新手入門

此為 程式導師實驗計畫第三期 裡的「[GIT101] Git 超新手入門」筆記

什麼是版本控制?

其實我們以前在做報告時就會遇到版本的問題,一份報告很難一次到位通常都會修正好幾個版本,但又不想取代現有版本,所以就會另存新檔並用新檔名儲存。

報告.doc
報告v2.doc
報告v3.doc
報告v4.doc
報告final.doc

像這樣將不同版本給予編號保存起來,就是一種版本控制。

為什麼需要版本控制?

如果今天我們每次修改報告裡的東西都直接儲存取代目前檔案,這樣就沒有版本的問題,因為永遠只有自己的這個版本。

如果報告是自己一人的可能沒有問題,那如果是多人協作的話呢?
每個人都可能修改報告,那最後要用誰的報告?怎麼知道我們兩份報告差異在哪裡?

所以需要版本控制

安裝

windows:下載 Cmder 這個 terminal 並選擇 full 才有包含 Git

Mac:直接輸入檢查版本的指令,如果沒安裝會引導你安裝

git --version

自己來做版本控制

可以想想如果是自己來做一個版本控制會怎麼做?

首先假設我們有幾個檔案

  • app.js
  • style.css
  • index.html

如果今天是用「檔案命名法的方式」,這樣會有個問題,例如當我用 app_v2.js 時,我不曉得要搭配哪一個版本的 style.css(不一定是最新的那個)

  • app.js
  • app_v2.js
  • app_v3.js
  • style.css
  • style_v2.css
  • index.html

我們其實可以用資料夾分版本的方式,這樣每個版本都有所有檔案且都是可以 work 的

v1/
  app.js
  style.css
  index.html

v2/
  app.js
  style.css
  index.html

如果我們有個檔案不想加入版本控制?那那個檔案就不要加入資料夾就好了。

test.txt

v1/
  app.js
  style.css
  index.html

v2/
  app.js
  style.css
  index.html

假如這是個多人協作的檔案,今天我在我的電腦新增一個版本的資料夾為 v3 ,而小明也在他的電腦也新增一個 v3 的資料夾,而我們裡面檔案都很不一樣,這樣就會有衝突。

怎麼解決?既然是名稱會衝突,那改個不會衝突的名稱就好了。
資料夾名稱變成「永遠不會重複」的 Hash 值(亂數)

98ersfueio/
  app.js
  style.css
  index.html
  
f834ufoier/
  app.js
  style.css
  index.html

這樣我新增的資料夾可能為 873riuerj,小明新增的資料夾為 8sviusdjfo,因為命名不同所以不會有衝突。

但是變成亂數後怎麼知道這些版本的順序?
那就另外開一個檔案記錄順序吧

1 98ersfueio
2 f834ufoier
3 8dsrhicfua

那怎麼知道目前的版本是用哪一個?也是專門開一個檔案紀錄

heurfheiurf

小結

如何製作一個優秀的版本控制軟體(?):

  • 需要新版本:開一個資料夾
  • 不想加入版本控制:不要加入資料夾
  • 避免版本號衝突:用亂數當作資料夾名稱
  • 最新版本:用另外的檔案紀錄

git 指令

git init 初始化

在你要做版本控制的資料夾下這個初始化指令,初始化過後,git 才能在這個檔案裡使用

git init

下這個指令後,它會跟你說 Initialized empty Git repository in /.../.git
也就是說它已經建立一個 .git 的隱藏檔在這個路徑下,這個 .git 就是 git 用來幫我們做版控的地方

如果你的 terminal 有裝特別的 theme,git init 後就會多了 master ,因為有了分支,預設就是 master 的分支。

同場加映:如何砍掉 git?

就是直接刪除 .git 檔,因為它是個資料夾,所以用 rm -r 刪除

rm -r .git

git status 查看狀態

git status

常常用來看目前檔案中 git 的狀態(我是誰?我在哪?)

這張圖就是說

我在 master branch,沒有東西可以 commit

不得不說 git 指令寫的真好,要做什麼、下一步是要打什麼都寫得很清楚,不怕忘記指令。

git add

git add <file>

決定哪些檔案要加入版本控制

git 下的每個檔案都有它的狀態,最一開始是 untracked (未被追蹤的),如果 git add 後會變成 staged

staged 就是放入準備區的意思,還不是正式變成一個新的版本。

可以一次 add 一個檔案,也可以一次加入多個

git add . // 所有檔案
git add *.js // 所有 js 檔

如果加錯了怎辦?

用 git status 查看有告訴你可以用 git rm --cached <file> 來 unstaged(不用特別背)

git commit

新建(提交)一個版本,也就是前面提到建立一個資料夾的意思

git commit 

直接 git commit 的話會開啟 vim 編輯器
如果只是簡短的訊息可以用 -m 就能一行解決

git commit -m ""

commit ㄧ定要留下 message,不然 git 不給過

git log

git log

用來看 commit 的紀錄(按 q 離開)

另外也可以加上 --oneline

git log --oneline

就會看到只剩一行,而且亂數會只剩下 7 碼

8b02825 (HEAD -> master) first commit

這個 7 碼很神奇的可以代替那個很長的亂碼,讓人很好奇那幹嘛要那麼長的

git checkout

回到過去的 commit

git checkout 8b02825

這裡要先知道你要回去的那個 commit 的 Hash 值是多少,所以通常可能會先用 git log 查 commit 的 Hash 值再 checkout

.gitignore 忽略檔

是一個檔案,裡面放入不想加入版本控制的檔名

例如 React 的 gitignore

Branch

為什麼需要 Branch

拿 Apple 的 OS 系統來舉例:

假設目前的 OS 版本是 13,這時蘋果工程師會繼續開發新功能,假設新功能自己多了很多 commit。結果目前的 OS 13 發現了一個很嚴重的 bug 需要立即修復,但目前的 commit 已經是加了很多新功能但還沒要上線的怎麼辦?

如果沒有分支,那就只能退回原本穩定版的那個 commit 去修 bug,那這些日子工程師寫的新功能怎麼辦?不就白寫了QQ

如果有分支,負責開發新功能的工程師可以繼續開發,而負責修 bug 的工程師可以找到穩定版的那個 commit 另外去修 bug,兩者不衝突且各做各的,完全就是平行時空。

git branch 新建分支

git branch:查看目前有哪些分支
git branch -v:除了分支名稱還會附上最新的 commit
git branch <name>:新建分支
git branch -d <name>:刪除分支

git checkout 切換分支

git checkout <branch name>

git merge 合併分支

假設我現在有 master 和 new-feature 兩個分支,那我想要做的是「把 new-feature 合併到 master」

以前超級常搞混到底是誰要 merge 誰,每次 merge 都戰戰兢兢,還好可能工作久了會自己有感覺(?)

我的方式跟 Huli 不一樣,我會自己先冷靜思考:我想做的事是「master 想要 new-feature 的東西」,那我就是 master,所以我要切到 master 去 merge!

git merge new-feature

conflict 衝突!

首先要知道什麼時候會發生衝突,主要是兩個分支改到同一個檔案的同一行,而且要有修正而不是新增

這樣不會衝突:

// master
i am master

// new-feature
hi i am master

這樣會衝突:

// master
i am master

// new-feature
i am feature

發生衝突後悔時:

可以回復 merge 前的時候

git merge --abort

github

git 與 github

Git 跟 github 是完全不同的東西(以前會搞混)

Git 是一個版本控制的軟體,版本控制軟體也不是只有 git 還有 其他的,只是 git 目前比較有名跟很多人用

github 是放 git repository 的地方,也不是只有 github,還有像是 gitlab、bitbucket 等

git pull & fetch

pull 跟 fetch 都是把遠端數據庫拉下來

  • pull:會自動合併!
git pull = git fetch + git merge
  • fetch:就只是把遠端數據庫拉下來,不會直接合併

git 狀況劇

我 commit 了但是想改 commit message

git commit --amend

我 commit 但我又不想 commit 了

首先先認識一下 HEAD,這是目前最新的 commit 的意思,而 HEAD^ 就是指上一個 commit

git reset HEAD^

所以這個指令就是要回復到前一個 commit 的意思

這個指令有兩個參數:

--soft:預設不帶就是這個參數,它會回復後還保留上個 commit 前做的事情
--hard:除了回復還把上個 commit 做的事都砍掉,好像從沒發生過任何事一樣

我還沒 commit,但我改的東西我不想要了

git checkout -- <file>

改 branch 名稱

假設今天想打 feature 但打錯打成 featrue(很常)

有兩種方式

  1. 切到打錯的分支 featrue 下
git checkout featrue
git branch -m feature
  1. 在其他的分支改
git branch -m featrue feature

切到遠端的 branch

有時候會發生遠端有的 branch 而我這裡沒有,這時很簡單,就跟一般在切 branch 時一樣用 checkout 就好了

git checkout <branch>

Git Hook

什麼是 hook?

hook 英文是鉤子的意思,在程式領域 hook 的意思指的是「發生某事的時候通知我」。

可以想像成 hook 是釣魚時的魚鉤,平時就擺在那,但魚上鉤時我就會被通知。

git hook

那在 git hook 主要是用來當進行一些操作,例如 commit、push 時可以做一些事。

.git 隱藏檔中裡有一個 hooks 的資料夾,裡面有放一些 shell 執行檔,例如有個 pre-commit 的檔案,就是可以在 commit 前做一些判斷看這個 commit 內容有沒有符合格式、有沒有上傳帳密(好奇這要怎麼看?)等等,如果不符合可以不給過。

[踩雷] ngrok subdomain issue

我知道這個問題應該很少人會碰到,但還是想記錄一下(這個愚蠢的問題)。😂

專案上有一個畫面是要透過取 subdomain 的值再去 call api 然後才 render,然後今天在改同個頁面的其他問題時,發現在手機上用 ngrok 測結果該畫面跑不出來。

本來以為是前面動作改壞了,但是前面改的東西其實跟該畫面沒什麼關係,總之我追了一陣子的問題才發現,是因為 ngrok 的 subdomain 不是我要的變數,所以取不到資料,當然畫面就無法 render。

延伸:

  • 是否做防呆?

原本我是這樣寫:

const type = Url.getSubdomain() || 'firstType';

Url.getSubdomain() 這是一個取 subdomain 的 function ,因為我們網站目前只有兩個 subdomain 跟沒有 subdomain 的情況 ,所以我只寫「假如取得到 subdomain 就直接傳該 subdomain 的值,如果沒有 subdomain 就預設給他 'firstType'。」

這樣寫雖然在我們網站可行,但是像想要在 ngrok 上測試就沒辦法用,應該還是要判斷 subdomain 是不是我們公司有的那兩個 subdomain ,如果不是也給它預設值。

if(Url.getSubdomain() === 'firstType' || Url.getSubdomain() === 'secondType') {
    const type =  Url.getSubdomain();
} else {
    const type = 'firstType';
}

20190131:Vue v2.6 Beta

2019/01/31

  1. Vue 釋出了 v2.6.0-beta.2
    這版是有關於 v-slot 的新增,可以從這個 https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md 看到他的改變,這是其中一項
<!-- old -->
<foo>
  <template slot-scope="{ msg }">
    {{ msg }}
  </template>
</foo>

<!-- new -->
<foo v-slot="{ msg }">
  {{ msg }}
</foo>

RFCs?
https://zh.wikipedia.org/wiki/RFC
請求意見稿
應該是在上線前想徵求其他人的意見

  1. Nuxt 也是出了 2.4.0
    https://dev.to/nuxt/nuxtjs-v240-is-out-typescript-smart-prefetching-and-more-18d?utm_campaign=Vue.js%20News&utm_medium=email&utm_source=Revue%20newsletter

最大的更新應該是可以寫 TypeScript 了

  1. https://dev.to/

這網站好可愛

  1. http://www.jsfuck.com/
    用六個字元組合就能寫程式

  2. 作者用蠻有趣的敘述分享什麼是 nextTick
    https://vuejsdevelopers.com/2019/01/22/vue-what-is-next-tick/?utm_campaign=vnt&utm_medium=post&utm_source=vuejsnews

Use it immediately after you’ve changed some data to wait for the DOM update.

Vue props 父子組件雙向綁定

我們很常用到 props 傳參數給子組件

<child :show="status"></child>

在子組件有時會想改變這個props的值
Vue都會有錯誤提示

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show"

在Vue 2.3版有個 sync
在父組件props加上 .sync

<child :show.sync="status"></child>

在子組件中需要更新時用一個 emit 的方法,不過要加上 update

this.$emit('update:show', false)

[學習筆記] React todo 流水帳

建立專案:create-react-app

npx create-react-app react-todolist

create-react-app 算是官方做好的一個懶人包,一鍵完成所有環境建置
功能相當於 vue-cli

yarn start

就會編譯完成並在預設 localhost:3000 開啟 server,並且是儲存直接 hot-reload

建立 Component

先建立 TodoList.js ,這是我們 TodoList 主要的 component
在 App.js 引入,render 裡也換上上 TodoList 的 tag

import TodoList from './TodoList';
...
class App extends Component {
  render() {
    return (
      <div className="App">
        <TodoList />
      </div>
    );
  }
}

TodoList.js

import React, { Component } from 'react' // component 必須有這一行

class TodoList extends Component {
    render() {
        return(
            <div className="todo-list">
                <h1>Todo List</h1>
            </div>
        )
    }
}

export default TodoList;

display todos

state

class App extends Component {
  state = {
    todos: [
        {
            id: 1,
            title: 'learn React',
            completed: false
        },
        {
            id: 2,
            title: 'make react todo app',
            completed: false
        },
        {
            id: 3,
            title: 'eat eat eat',
            completed: false
        }
    ]
}
  render() {
    return (
      <div className="App">
        <TodoList />
      </div>
    );
  }
}

React-devtool
https://github.com/facebook/react-devtools

2019-02-12 10 48 25

props

 <TodoList todos={this.state.todos}/>

換成vue的寫法為

 <TodoList :todos="state.todos"/>

將 todos 印出來

return this.props.todos.map((todo) => (
            <h3>{ todo.title }</h3>
        ))

建立 TodoItem

TodoItem.js

export class TodoItem extends Component {
  render() {
    return (
      <h3>{ this.props.todo.title }</h3>
    )
  }
}

TodoList.js

class TodoList extends Component {
    render() {
        return this.props.todos.map((todo) => (
            <TodoItem key={todo.id} todo={todo} />
        ))
    }
}

propTypes

props的規範

在 TodoList 下定義自己的props,要先 import PropTypes

import PropTypes from 'prop-types';
...
TodoList.propTypes = {
    todos: PropTypes.array.isRequired
}

覺得 vue 比較直覺一點

export default {
    props: {
        todos: {
            type: Array,
            required: true
        }
    }
}

如果不強制type和required,也可以直接用array方式列出: props: ['todos']

inline style

style要用兩個大括弧,css 要使用 Camel case

<div style={{ backgroundColor: '#eee' }}>
    <h3>{ this.props.todo.title }</h3>
</div>

與 vue 的對比
(參考:https://vuejs.org/v2/guide/class-and-style.html#Object-Syntax-1)

<div :style="{backgroundColor: '#eee' }">
</div>

第二種方式:建 variable

<div style={itemStyle}>
        <p>{ this.props.todo.title }</p>
      </div>
const itemStyle = {
  backgroundColor: '#eee'
}

第三種:function

export class TodoItem extends Component {
  getStyle = () => {
    if (this.props.todo.completed) {
      return {
        textDecoration: 'line-through'
      }
    } else {
      return {
        textDecoration: 'none'
      }
    }
  }
  render() {
    return (
      <div style={this.getStyle()}>
        <p>{ this.props.todo.title }</p>
      </div>
    )
  }
}

改變狀態:setState

TodoItem.js

const {id, title} = this.props.todo;
<input type="checkbox" onChange={this.props.markComplete.bind(this, id)}/>

當 checkbox 改變時 call function,但這個 function 必須放在 state 有 todos 那層,並透過 props 傳遞 function(vue 不是用props 傳遞function,而是子層 emit 事件傳遞給父層)
注意這裏的 function 要夾帶變數的話要用 bind,第一參數必須是 this,第二參數才是要傳遞的變數

app.js

markComplete = (id) => {
    this.setState({
      todos: this.state.todos.map(todo => {
        if(id === todo.id) {
          todo.completed = !todo.completed
        }
        return todo;
      })
    })
  }

利用 setState 改變 state

雙向綁定

vue 很簡單,只要用 v-model 即可

<input type="text" v-model="title"></input>
...
data() {
    return {
        title: ''
    }
}

react 是怎麼實現呢

state = {
    title: ''
  }

  onChange = (e) => {
    this.setState({title: e.target.value})
  }
...
<Input placeholder="Add Todo..." name="title" style={{width: '100%'}}
            value={this.state.title} onChange={this.onChange}/>

這樣就完成雙向綁定了

接下來要把新的 Todo 新增到 Todos 陣列

<Button type="submit" onClick={this.onSubmit}>submit</Button>

先建立 submit 事件

onSubmit = (e) => {
    e.preventDefault(); // 不執行原生的 submit 事件
    this.props.addTodo(this.state.title);
    this.setState = {
      title: ''
    }
  }

一樣執行從 App.js props 下來的 addTodo 事件

App.js

addTodo = (title) => {
    const newTodo = {
      id: 4,
      title,
      completed: false
    }
    this.setState({
      todos: [...this.state.todos, newTodo]
    })
  }

uuid

這邊就有個問題了,newTodo 的 id 要怎麼給呢?
不能給固定的,因為 id 不能重複
這時就要找一個可以製造 id 的工具「node-uuid」https://github.com/kelektiv/node-uuid
選擇版本4就能產生隨機的 id
將所有用到 id 的地方直接用 uuid.v4() 取代

import uuid from 'uuid';
...
const newTodo = {
      id: uuid.v4(),
      title,
      completed: false
    }

Router

接下來
下載 react-router-dom
https://github.com/ReactTraining/react-router/tree/master/packages/react-router-dom

import React from 'react'

export default function About() {
  return (
    <React.Fragment>
      <h1>About</h1>
      <p>This is a todo app by React.</p>
    </React.Fragment>
  )
}

React.Fragment 應該就像 Vue 裡的 template ,是一個虛擬的 DOM 節點

引用 react-router-dom
import { BrowserRouter as Router, Route } from 'react-router-dom'

<Router>
        <div className="App">
          <Header />
          <Route exact path="/" render={props => (
            <React.Fragment>
              <AddTodo />
              <TodoList todos={this.state.todos} markComplete={this.markComplete}
                deleteTodo={this.deleteTodo} addTodo={this.addTodo}/>
            </React.Fragment>
          )} />
          <Route path="/about" component={About} />
        </div>
      </Router>

串 api

接下來要串 api
用 json placeholder 來串假資料
https://jsonplaceholder.typicode.com/todos

用 axios
https://github.com/axios/axios

取得資料 get

componentDidMount() {
    Axios.get('https://jsonplaceholder.typicode.com/todos?_limit=10')
    .then(res => this.setState({todos: res.data}))
  }
...
state = {
    todos: []
  }

componentDidMount 就是 vue 裡的 mounted
componentWillMount 就是 vue 裡的 created
試過將 axios get api 這段放在 componentWillMount 裡也是可以的(跟 vue 裡可以放在 created 裡一樣)

post 資料

接著,我們要透過 post 新增 todo

addTodo = (title) => {
    Axios.post('https://jsonplaceholder.typicode.com/todos', {
      title,
      completed: false
    })
      .then(res => this.setState({
        todos: [...this.state.todos, res.data]
      }))
  }

deploy 部署

最後就是要部署了
參考 https://codeburst.io/deploy-react-to-github-pages-to-create-an-amazing-website-42d8b09cd4d 部署至 github-pages

部署是成功了,但是遇到了一個問題
https://medium.com/@Dragonza/react-router-problem-with-gh-pages-c93a5e243819

前端自己來!用 json server 做 RESTful API

json server

可以做什麼

當我們前端在開發時,會碰到需要透過 API 取資料的時候,這時如果公司的後端還沒開 API 出來就沒辦法做了,或是自己在練習 Project ,沒有後端可以產 API

這時候就是前端當自強了!

json server 就是可以幫我們建立一個 api server,並能用 RESTful 的方式取資料
你需要準備一個放資料的地方,可以是 js 或 json 檔

json server

首先全域安裝 json server

npm install -g json-server

在資料夾底下新建一個放資料的地方,可以是 json 或 js 檔(js 檔必須 export json 格式)
裡面放假資料,假資料可以用 faker.js 或 Mock.js 來產生
以官網的範例,取名為 db.json,內容如下

{
  "posts": [
    { "id": 1, "title": "json-server", "author": "typicode" }
  ],
  "comments": [
    { "id": 1, "body": "some comment", "postId": 1 }
  ],
  "profile": { "name": "typicode" }
}

開啟 json server,並且指定使用哪個檔案

json-server db.json     

成功開啟 server 會出現如下圖,還很貼心寫出有哪些 api 可以使用

2019-02-24 4 31 35

在專案裡便可以直接使用這個 API

Axios.get('http://localhost:3000/posts/')
          .then(res => {
            console.log(res.data);
          })

// response: 
[
        //     {
        //        "id":1,
        //        "title":"json-server",
        //        "author":"typicode"
        //     }
        //  ]

也可以使用 POST,可以看到 db.json 也變成了兩筆資料

Axios.post('http://localhost:3000/posts/', {
            id: 2,
            title: 'hell0',
            author: 'Debby'
          })
          .then(res => {
            console.log(res.data);
          })

不只如此,我們取值甚至可以做 filter 跟 sort:

GET\ http://localhost:3000/posts?author=Debby

[
    {
        "id": 2,
        "title": "hell0",
        "author": "Debby"
    }
]

還有很多,可以看文件 https://www.npmjs.com/package/json-server

jsonplaceholder

https://jsonplaceholder.typicode.com/

另一個服務,我覺得很適合作為前端 CRUD 的練習
總共提供6種 api
/posts 100 posts
/comments 500 comments
/albums 100 albums
/photos 5000 photos
/todos 200 todos
/users 10 users

假設今天做了一個 todolist ,就可以用 get https://jsonplaceholder.typicode.com/todos 去獲得資料

社群分享功能超簡單

本來覺得要做社群分享可能還需要跟 Facebook 或 Line 申請之類的,本來想說可能會有點麻煩,但其實很簡單又快速!

Facebook 分享

首先到官方的 分享按鈕 頁面,輸入我們想複製的網址再按取得程式碼

例如我輸入的網址是:https://github.com/jijigo/notes/issues

這邊是給要嵌入 Facebook 自己的分享按鈕的程式碼,如果像是我有自己做的按鈕,只是想有分享的連結的話就是複製他裡面的這段 url 就好囉

https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fgithub.com%2Fjijigo%2Fnotes%2Fissues&amp;src=sdkpreparse

是不是超簡單 der

Line 分享

ㄧ樣先到 Line 官方的 social plugin 頁面,上面有特別寫「自訂圖標」的連結如下:

https://social-plugins.line.me/lineit/share?url={encodeURIComponent(URL)}

{encodeURIComponent(URL)} 這邊替換成經過 encode 的網址就可以囉

[css] select default style in safari

問題

select在Chrome裡很正常,但在Safari會有一個灰色的背景,使用background: none都沒用

Chrome

Safari

這時加上這行即可

-webkit-appearance: none;

stackoverflow

appearance是什麼?

appearance 是一個css的屬性,用來描述一個元素的外型,不同的瀏覽器可能有不同的appearance
根據css-tricks

通常你會用到它有兩種可能

  1. 某個元素在某個瀏覽器下沒有我們要的樣式,要加上去
  2. 某個元素在某個瀏覽器下有我們不要的樣式,要移除

上面遇到的問題就是safari的樣式不是我們要的,要另外把它移除

參考資料

css-tricks-appearance

如何新增 subdomain 到 localhost:修改 Mac hosts file

平常時做專案都在 localhost 的某個 port 上
但如果有 subdomain 的需要
頁面就會顯示 找不到 xxx.localhost.com 的伺服器 IP 位址。

因為我使用 Mac OS,所以以 Mac OS 操作方式介紹為主

首先打開 terminal

sudo vi /etc/hosts

sudo (https://zh.wikipedia.org/wiki/Sudo) 指可以允許使用者使用特殊的權限執行程式,所以執行時會詢問使用者密碼
vi (https://zh.wikipedia.org/wiki/Vim) 是一個編輯器
後面就是路徑了

整句話的意思就是:使用特殊權限開啟 hosts 檔案並用 vim 編輯。

輸入完密碼後,就出現了 hosts 檔,可以看到 terminal 的 tab 變成 vim,表示已經變成 vim 模式
然後會發現都沒辦法打字,因為現在是唯讀模式
直接按「i」,下方會出現「--INSERT--」表示變成了編輯模式

直接加上一行

127.0.0.1 xxx.localhost.com

輸入完後按 「esc鍵」就能跳出編輯模式,離開則是按「:wq」
就回到原來的 terminal ,這樣設定就完成了

到網頁打上剛剛設定的網址就有畫面囉

詳細的 vim 指令可看 http://www.vixual.net/blog/archives/234

reference:
https://blog.gtwang.org/windows/windows-linux-hosts-file-configuration/
https://stackoverflow.com/questions/19016553/add-subdomain-to-localhost-url

Note for webpack 新手教學之淺談模組化與 snowpack

這是我自己的筆記 for webpack-新手教學之淺談模組化與-snowpack

1. 如何在 node.js 使用模組?

你應該會常常看到 requiremodule.exports

例如下面這個例子,有個 main.js 想要使用另一隻 utils.js 裡的 add function,首先要先將 add function 透過 module.exports 丟出去

// utils.js

function add (x, y) {
  return x + y
} 

module.exports = add

在想要使用該 function 的地方用 require 引用進來,就可以使用了

// main.js
var add = require('./utils')

add(1, 2)

2. CommonJS 標準說的就是 requiremodule.exports

這是因為在 ES6 前 javascript 沒有針對模組使用方式有任何規範,於是就有人發明這套規範,並被 Node.js 採用

所以在 Node.js 可以直接使用 requiremodule.exports,但在瀏覽器上不行,如果你可以在瀏覽器上使用,應該是你用了 webpack 或其他打包工具幫你完成的

3. 試試看在瀏覽器上跑 CommonJS 規範

首先把原本是分開檔案的 main.js 和 utils.js 各包成 function,其中 require 和 module 當成參數傳入

modules 包成一個 object,require 則是一個 function

以下的實作是參考原文章(可以直接貼在瀏覽器 console 跑)

function main(require) {
  var utils = require('./utils')
  console.log(utils.add(1, 2)) // 3
  console.log(utils.name) // I'm good
}

function utils(module) {
  function add(x, y) { 
    return x + y
  }

  module.exports = {
    add: add,
    name: 'I\'m good'
  }
}

var modules = {}
utils(modules)

function require() {
    return modules.exports
}

main(require)

實作後,有個地方有點小困惑,就是 require('./utils') 這裡的 './utils' 其實沒作用,以這邊的程式碼直接 require() 就可以了

所以我的疑惑是,應該怎麼實作才能在更正確一點,在原文章裡的有參考了 browserify 的 source code(原文章還有簡化程式碼的那段,整理很清楚)

看起來像是在 modules 的 obj 加上 namespace,所以我稍微改寫了一下這段程式碼如下:

function main(require) {
  var utils = require('./utils')
  console.log(utils.add(1, 2)) // 3
  console.log(utils.name) // hello
}

function utils(module) {
  function add(x, y) { 
    return x + y
  }

  module.exports = {
    add: add,
    name: 'I\'m good'
  }
}

var modules = {
    "./utils": {}
}
utils(modules['./utils']) // 

function require(name) {
    return modules[name].exports
}

main(require)

4. browserify 是啥

剛剛提到的 browserify,其實就是蠻早期的打包工具

Browserify lets you require('modules') in the browser by bundling up all of your dependencies.

只要一個指令,打包出來的 js 就能在瀏覽器上跑

browserify main.js -o bundle.js

5. webpack vs browserify

webpack 也是打包工具,但功能更多

需要寫一個 webpack.config.js(所有打包的需求都會寫在這裡面,這只是冰山一角ㄋ)

module.exports = {
  entry: './main.js', // 需打包的檔案
  output: {
    path: __dirname, // 打包後的路徑
    filename: 'webpack_bundle.js' // 打包的檔名
  }
}

然後ㄧ樣跑一行指令

npx webpack --config webpack.config.js

6. ES6 的模組 import/export

上面說的 CommonJS 是有人自行開發,被 Node.js 採用,但一直都不是 Javascript 本身的標準

一直到 ES6 終於有一套自己的標準了,那就是 import/export,上面的範例改起來就是這樣

// utils.js

function add (x, y) {
  return x + y
} 

exports default add
// main.js
import add from './utils'

add(1, 2)

但是!支援度還不夠好,在 Node.js 上跑的話會報錯,如果要在 Node.js 上跑,有兩種選擇

  1. 副檔名從 .js 換成 .mjs,然後加上 flag node --experimental-modules main.mjs
  2. 使用 babel 線上轉換器

如果是瀏覽器呢?

  • 在 html 引入時要加上 type="module"
<script src="./main.js" type="module"></script>
  • 在 import 時,要把副檔名打出來
import add from './utils.js'

7. import / export 在瀏覽器上的問題

雖然看似很美好,但還是有幾個問題

  1. 瀏覽器支援度

雖然很多瀏覽器都有支持,但 IE 目前還是沒有支援(MDN - import

  1. 使用 npm 其它套件

使用別人套件時,import 時的路徑無法寫,可能會長這樣:import pad from './node_modules/pad-left/index.js',但維護性極差

ps. 如果真的要使用其他人的套件又不想打包,有一招大絕招就是使用 cdn 引入 script

8. webpack 幫你做

上面提到的問題,使用 webpack 也可以解決。webpack 可以把 npm 其它套件當成我們自己寫的 modules ㄧ樣一起打包

webpack 還能做很多事,這邊 copy 了一下原文:)

  • 將圖片和 css 也模組化(需要 loader)
  • 在載入 JS 的時候順便做 uglify
  • 在載入 CSS 的時候順便做 minify
  • 把打包出來的檔名順便加上 hash
  • 根據不同頁面打包不同的檔案,就不用一次載入全部 JS
  • 支援動態引入 JS,有需要的時候才載入

9. 新的打包工具:Snowpack

一個新的打包工具 Snowpack,被稱為輕量版的 Webpack

主要是把 npm 套件整理一下放到 web_modules 這個資料夾下,所以引用的路徑就變成 import htm from '/web_modules/htm.js'; ,不過不是所有套件都可以使用,套件需要支援 ESM module 才行

使用方式很簡單,一樣安裝玩所需的套件後,一鍵 npx snowpack 就會看到 web_modules 資料夾出現,import 路徑就從 web_modules 取得,然後 script 要加上 type="module"

這個工具也能解決我們前面提到的兩個問題,瀏覽器問題主要是看 type="module" 的支援程度,目前有支援到 IE11

結語

原本對 CommonJs 、 require/modules.exportimport/export 這幾個的關係說真的沒有很清楚,這篇文章算是有解答到我的一些疑惑!

(雖然找資料時看到 AMD 跟 CMD 還是有點問號)

Resources

DOM 事件傳遞機制

為什麼會有這個機制

簡單來說
在 dom 裡是一層包一層
例如一個 ul 包 一個 li
而我們在這兩個 上都綁定了 eventlistener
那當我們點了內層的物件
也同時點了外層的物件
這時誰會被先觸發事件?
所以就有了這個事件傳遞的機制
這個機制可以說是定義了「事件傳遞的順序」

來看實際的例子
jsbin 連結

<div class="parent">
    parent
    <div class="child">
      child
      <a href="" class="link">link</a>
    </div>
  </div>

<script>

const parent = document.querySelector('.parent')
const child = document.querySelector('.child')
const link = document.querySelector('.link')

parent.addEventListener('click', (e) => {
  alert('parent')
})

child.addEventListener('click', (e) => {
  alert('child')
})

link.addEventListener('click', (e) => {
  alert('link')
})
</script>

這個例子中,我們最外層有一個 parent ,在他裡面有個 child,child 裡面還有 link
當我們點紅色區塊,也就是 parent 的部分,會跳出一個 alert,內容是「parent」
當我們再點黃色區塊,會跳出兩個 alert,會看到先「child」再「parent」
最後我們點 link 區塊,會分別跳出「link」「child」「parent」的 alert

為什麼?

為什麼點 child 會跳出兩個 alert?
我明明只點了 child,跟 parent 有什麼關係呢
還有,為什麼 alert 是先出現 child 再出現 parent 呢?

事件的三個階段

一個事件(例如一個 click 事件)其實會有三個階段

  1. Capture phase(捕獲)
  2. Target phase
  3. Bubbling phase(冒泡)

image

看這張圖可以很清楚的看出來
雖然他真正的目標是 <td>
但事件都會從根節點 window 開始往下傳
會一路經過包住我們目標的節點,這個時候是 Capture phase
到達我們目標的時候則是 Target phase
接下來會再將事件往上傳遞,傳回根節點,此時為冒泡階段

以剛剛的例子來說
我點了 link
這個事件會從根節點 window 向下傳,然後傳到我們的 parent 時,如果 parent 這時也有事件就會被觸發,而這時 parent 的事件階段為捕獲階段
接著傳到 child ,此時也是捕獲階段
接著到了我們的目標 link ,此時為 Target phase
到了目標後事件會往上傳
先到 child 此時變成冒泡階段、傳到 parent 一路傳回 window 。

根據上述例子可以發現
除了我們的目標 link 以外,其他 child 跟 parent 都會經過兩次(一次捕獲一次冒泡)
當我們綁定事件在上面時,他怎麼知道我們要在捕獲還是冒泡階段被觸發呢?

神秘的 eventListener 的第三個參數

https://developer.mozilla.org/zh-TW/docs/Web/API/EventTarget/addEventListener

百分之99 我們的 eventListener 會這樣寫

link.addEventListener('click', (e) => {
  alert('link')
})

第三個參數為 useCapture
它是個 Boolean 值
顧名思義:要在捕獲事件觸發嗎?
預設值為 false

所以我們平常沒有給它值的話,除了他本身是目標以外,都會是在冒泡階段觸發
這也就是回到前面的例子的問題

「為什麼點 child, alert 是先出現 child 再出現 parent 」

原因就是,因為我們沒給第三個參數,所以預設在冒泡階段觸發

eventPhase 查看目前階段

我們在事件中可以透過 eventPhase 來取得目前的階段
這個 eventPhase 的值為數字
1 為 capture phase,2 為 target phase,3 為 bubble phase
https://developer.mozilla.org/zh-TW/docs/Web/API/Event/eventPhase

parent.addEventListener('click', (e) => {
  console.log('parent', e.eventPhase)
})

child.addEventListener('click', (e) => {
  console.log('child', e.eventPhase)
})

link.addEventListener('click', (e) => {
  console.log('link', e.eventPhase)
})

當我們點 link 時,可以看到順序是 link、 child、parent,除了 target link,

"link"
2
"child"
3
"parent"
3

如果我們加上第三個參數,並把值改為 true 呢

parent.addEventListener('click', (e) => {
  console.log('parent', e.eventPhase)
}, true)

child.addEventListener('click', (e) => {
  console.log('child', e.eventPhase)
}, true)

link.addEventListener('click', (e) => {
  console.log('link',e.eventPhase)
}, true)

可以看到順序跟前面相反

"parent"
1
"child"
1
"link"
2

小結

  1. 所以一個事件只能選擇捕獲或是冒泡時觸發嗎?
    A:沒錯,沒辦法只寫一個事件卻同時兩個都要!如果都想要偵測的話,只好寫兩個事件

  2. 一定要同樣的事件才會被觸發嗎?
    A:沒錯,例如目標是 click 事件,那父層的也是 click 事件才會被觸發

  3. target 沒有捕獲或冒泡階段

如何阻止事件傳遞

通常阻止的情況就是
像上面的例子,我點了 link ,但我不想觸發父層的 child parent
這時我要在 link 的事件加上 e.stopPropagation()

link.addEventListener('click', (e) => {
  console.log('link',e.eventPhase)
  e.stopPropagation()
})
"link"
2

這時就會中斷後面的冒泡事件
e.stopPropagation() 是加在哪,整個事件傳遞就會斷在哪

source

DOM 的事件傳遞機制:捕獲與冒泡 (史上最詳細解說)

vue中用refs取得component值出現undefined

我遇到的情況是,想要到某個頁面時把從php抓到的資料塞進 form-input 這個component中

此為引用component的頁面

...
<form-input type="tel" ref="phoneInput" v-model="phone">
    <template slot="label">手機號碼</template>
</form-input>

這邊是js的部分,在 mounted 中塞值進去

mounted: {
    this.$refs.phoneInput.inputValue = this.userInfo.user_phone || '';
}

但會得到這樣的error

Error in mounted hook: "TypeError: Cannot set property 'inputValue' of undefined"

在網路上找很多相關資料都找不太到解答,後來終於在 這篇 找到解答
在 mounted 這個階段時,component還沒加載進來,所以 this.$refs.phoneInput 這會得到undefined

解決方法為使用 $nextTick

this.$nextTick(() => {
    this.$refs.phoneInput.inputValue = this.userInfo.user_phone || '';
});

如果寫在 updated 階段也是可以的,但是每次資料更新都會跑一次,這也不是我要的
總之最後用 $nextTick解決了!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.