2023年12月18日 星期一

記錄一下這四天的奇幻旅程

前言

因為有點空檔,所以想說可以在線上找個pt工作賺點外快,所以我在網上聯絡到一個HR(老闆?PM?),因為他從未表達他的身份所以我也無從得知,而且他回話總是很慢,留言給他大概都要半天後才會得到回覆,總之他給我一小時500的費用讓我試著做做看。之後為了方便辨識,我都稱呼他為A先生

他們公司有自行開了一個看板系統,可以自行上去認領要做的任務,打上評估時間,只要通過審核之後就可以開始開工。

首先A先生派了一個專案給我,因為我週末有安排,中間抽空看教學,花了兩到三天的時間,最後因為該環境限制要amd64架構的cpu(裡面有要架mssql),我用的是mac arm64 cpu所以最後還是沒架起來,一直到週日晚上,A先生又給了我另外一個專案,我花了一個早上時間把環境架起來,然後開始看任務,但任務卡上幾乎沒什麼說明,所以我就開始提問,然後又是半天一回,最後他還補了一句:12/25要進行驗收,我說:等一下,我這週要出國,這我之前就說過了!他回:沒關係,有多少做多少!

講是這樣講,我也很想趕快做點事情,但是我完全看不懂任務看板,我也不知道PM是誰,總之A先生就是我聯絡的窗口,理所應當我也所有問題都問他,但這效率真的不行,你們要後端開發系統,不用開個會交代一下嗎!你是真的覺得一張表格圖就可以搞懂系統全貌那也真的太高估我了。搞得我自己壓力很也大,經思考我覺得好像這不是一個很健康的狀況,我花了三天的時間,雖然不是全時段在線,但全部加總也花了不少時間,我一行程式碼都沒寫到,這些完全是無償的,所以為了長遠著想,並且為了出國可以放心遊玩,所以這是我人生第一次只上班四天就離職,真D雷~

不幸中的大幸,我在這過程中還是有學到一點東西,做一下紀錄

VSCode有個套件叫Dev Containers,安裝完後左下角會有個類似><的綠色按鈕


這圖案點下去會出現幾個選項

New dev container, Attach to running container...

這次使用到第二個,意思是直接進到container裡面進行開發,這樣就可以在php有版本限制的情況下開發了,算是只要想辦法把理想的開發環境build成image就好,減少個體環境的差異。

另外現在在container裡面怎麼連外面的服務呢?在terminal匡那邊有一個ports的tab,可以在裡面設定VSCode port forward,夠過VSCode連結到外面的服務,這構想也算很酷了,如果不是這次體驗我大概率一輩子也不會用到。

後記

這是我摸的第一個專案,還有第二個,第二個就單純一點,laravel10的專案,結合inertiajs,變成一個全端框架但.....我覺得很難用,比較酷的是vite也有出laravel的plugin讓前後端結合得更好,但~有空再來說吧

2023年12月13日 星期三

有趣的小東西MutationObserver

這應該也不是什麼太新鮮的玩意兒,2019年的產物,無意間在我的最愛翻到的,跟大家分享下
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script>
const div = document.querySelector("div");
const observer = new MutationObserver(function (mutations) {
mutations.forEach((record) => {
console.log(record);
});
});

observer.observe(div, {
childList: true,
attributes: true,
characterData: true,
});

function changeContent() {
div.textContent = "Hi I'm Tom";
}

function changeColor() {
div.style.color = "red";
}
</script>
</head>
<body>
<button onclick="changeContent()">改變內容</button>
<button onclick="changeColor()">改變顏色</button>
<div>Hi I'm Max</div>
</body>
</html>

簡單說他可以偵測DOM的變化,有變化就觸發方法,以現在前端框架滿天飛的時代,好像沒啥鳥用,不過有趣就記錄一下。

2023年12月10日 星期日

proxy遇到lagacy browser的小坑

前言

還記得前幾天我用signal包了react用的vue hook套件嗎?才講完沒試過兩天就出事了

不過好里加在不是什麼大包,事情是這樣的

內文

有個Android 8 chrome 70 webview的用戶(簡單說就是android的webview)回報資料顯示有問題,我看這版本起碼也是七八年前的產品,但照道理來說都已經支援Proxy了怎麼可能會出什麼大錯,再track bug的過程中發現,我寫的資料陣列被過濾掉了

const buildPaymentDataByCType = (paymentConfig) => {
// payment is proxy object
const payment = initData.withdrawList.find((x) => x.CType === paymentConfig.C_TYPE)
    // console.log(payment) // exists
return
{
... payment,
// ellipsis
},
)
}
const normalWayPlatforms = computed(() =>
[
buildPaymentDataByCType(ORDER_PAYMENT.BANK),
].filter((x) => x.Name)
)
console.log(normalWayPlatforms.length) // 0??

我在console.log(payment)都是有東西的,理論上應該都要找得到相對應物件,但為什麼會被filter掉,所以我試著不要filter直接在console.log出來,發現問題出在{...x}擴展運算子並沒有生效!!

難道是老舊瀏覽器不支援這ES6語法?所以我又隨便寫了一個

const a = { a: "a" }
console.log({ ...a })

發現這樣寫是正常的,所以應該只是因為Proxy,所以多了一層,然後擴展運算子當時還不支援擴展Proxy,94這麽簡單。那這樣要怎麼搞定呢?

我嘗試一下Object.assign還是可以把Proxy擴展出來,所以就改成這樣

const buildPaymentDataByCType = (paymentConfig) => {
const payment = initData.withdrawList.find((x) => x.CType === paymentConfig.C_TYPE)
return Object.assign(
{
// ellipsis
},
payment
)
}

OK打玩收工

後記

過程中我有嘗試用lagacy plugin把程式碼轉成es5但似乎都沒有成功,vite似乎不希望程式碼可以編譯到es6以下,總之vite還是有他不方便的地方啊,祝大家都可以準時下班~

2023年12月9日 星期六

針對某公司前端面試做個紀錄

前言

我絕對不會說我去面試K什麼M什麼體什麼X的公司,整體面試感受不太好
HR不夠積極跟友善,面試前一小時臨時改面試時間,面試開始前八分鐘才叫我提前五分鐘到,結果面試官也是準點進來,然後面試官整場用我不熟悉的語法(純粹大陸用語跟台灣用語不同)來問問題,所以很多問題我也只能重複進行確認,然後問完話也沒有要讓我提問,說沒有問題了匆匆結束會議,然後也沒有打算讓我跟HR二面,就在你問我答,足足問了22分鐘結束面試....好吧,非常有效率給你一個讚!

內文

針對被問幾個我印象比較深刻或回答沒這麼好的問題進行一次紀錄

1. 輸入網址到畫面渲染,中間過程如何....
又是這種沒有標準答案的鳥問題

2.vue v-for跟v-if可以一起用嗎?
我回答可以,對方接著問誰先誰後,我說太久沒用我忘了,試過就知道(但對方顯然對這答案不買單)
最近去翻筆記,確實是可以,然後在底層v-for會先執行再執行v-if

3.js的垃圾回收機制是什麼?
老實說我會寫不會記憶體洩漏的程式,所以從來沒想過這問題,所以我去查一下
答案是沒有被引用的物件會被瀏覽器回收,所以如果有物件相互引用就會造成記憶體洩漏(memory leaks)

4.Promise的狀態有什麼?
又是一題會寫但不知道答案的問題,我用這麼久還沒遇過有人問這問題,去查一下答案是
pending執行中
reject操作失敗
resolve操作成功

5.react useRef可以用來幹嘛
記錄變數,指向DOM

6.react useState, useRef,當state更新但畫面沒有更新,可以透過ref回復到先前狀態嗎?
我回答:理論上是可以啊。對方突然很驚訝回我:可以!那要怎麼做。我:就看你邏輯怎麼寫吧...他的回答讓我感覺我的回答是錯的,這就是沒有程式碼在那邊盲問的缺點,另外我倒是很想問你是怎麼做到更新state然後畫面沒有更新

小結

其實他還問了很多,但大多我都會答,應該是都對拉,雖然最後我被打上T3的等級(對方要T4),然後被大砍期待薪資,不過貴公司996的工作時數我就算沒被砍我也大概率不會進去(是真的996),然後工時打在JD上我以為是常識,只能說我真的孤陋寡聞,還是在我面試完後拼命追問HR才告訴我。只能說這間公司除了遠端,其他沒有任何一點吸引我的地方

2023年12月8日 星期五

nginx X-Frame-Options & proxy

前言
我都覺得我快變成維運了,全端工程師已經夠可憐了,最近寫的文章全部都是為運相關....

內文
好好一個案子說“弱掃”掃描不過有安全性問題

其中一條說
Missing Anti-clickjacking Header: 在HTTP Header加入X-Frame-Options: DENY或SAMEORIGIN
然後附上一段參考網址
https://a42033.gitbooks.io/system/content/security/user/X_Frame_Options.html

我去研究一下發現這是關於自己往也能否被iframe嵌入的設定,理論上當然要不行,或者同域名才可以
所以我參考這個網頁

因為我伺服器是用nginx,所以我就只打關鍵字拉
在default.conf裡面加上
add_header X-Frame-Options DENY;
這樣就搞定了。


至於還有一個比較大條的,安全性等級比較高的問題寫到

Cloud Metadata Potentially Exposed
參考網址
https://www.nginx.com/blog/trust-no-one-perils-of-trusting-user-input/

老實說他只舉出錯誤的寫法,沒有指出正確該怎麼寫.....所以咧~~
最後我看留言,有一個可憐的老哥跟我有一樣的問題:所以呢,是要怎麼解決!!然後他給出他修改的範例,請作者指教。好在作者還真的有回應他,並且是正向的。(媽的他寫對就說一句“對”很難嗎,我看完google translate文鄒鄒落落長,都不知道你在說什麼)

簡單結論一下,文章指出如果直接寫
proxy_pass http://$host;
這樣會有相對的風險,官方“不建議”這樣寫,所以比較好的寫法是透過upstream去指定host
upstream a_node_app {
server 127.0.0.1:3011;
}
我記得這是用來做low balance的,所以就是我指定到upstream的一個host,再由他幫忙轉到我要的位址,這樣就可以了。

後記
文章給的另一個解法是可以架防火牆,至於怎麼用....等我哪天真的轉維運再告訴你

2023年12月3日 星期日

我受夠了react的hook,我想寫vue啊~

前言

你是否有跟我一樣的困擾呢,覺得react class component的setState寫法相當繁瑣,覺得react hook的的queue也不夠直覺,為什麼不能直接update state然後畫面就自動render需要透過一個setter,然後資料異動還不是同步的,也不是異步,竟然是透過queue......這是我一直以來的心聲。我今天沒有要戰,純粹是就職以來的一些小心得,作為一套老牌框架,我承認他的市佔率,但我覺得如果以便利性讓我挑選,我一定不會選react,但礙於現有的社群要他大改幾乎是不太可能,就算不論三大框架現在市場上也充斥著太多套創新的框架(qwik, svelte, solid, astro....),所以要用一個形容詞來形容這套框架只能說它“過時”了,but!最可怕的but來了,公司就愛用嘛~所以有沒有什麼辦法?

老實說我很驚訝竟然查不到相關的資料,還是大家都超滿意react,沒遇到什麼奇怪的坑,我是異類,總之~辦法還是有的,首先我在腦海中搜索想到了奶綠茶有介紹過preact/signal,奶綠大大事我很喜歡的一位工程師,我大學時還買過他的書,不過那是另外一個故事了

內文

preact跟react是不一樣的框架,但其中的api友87%像,signal使用起來就像是vue裡面的ref(太詳細的內容自己去看原始碼),然後看網誌說明signal無法直接使用在react,特別是底層是vite打包的狀況下(該死)

所以不得已我又開始尋找其他法,後來發現preact/signal有出preact/react-signal特別優化的版本,這樣應該是妥了吧,官方都直接出優化了,直接使用在公司專案結果爆出getSnapshop is undefined......我去查了一下原始碼也一無所獲,歸功於react亂七八糟......抱歉我不該戰,是莫名其妙...抱歉,是我孤陋寡聞不得而知的底層運作機制,所以我放棄了這麼一條路

難道我就該止步於此繼續使用這該死又難寫......抱歉我不該戰,我要繼續使用這我用了很痛苦的框架嗎?當然不是,既然連官方都靠不住,那就只能自己來寫了,一開始我嘗試使用Object.defineProperty去寫(沒錯就是vue2那套,想看原始碼也附註解在裡面),反正所有東西都丟到value下面就好了吧,但也發生了跟vue2一樣的問題,沒有註冊過的屬性就不會reactive,但如果使用Proxy怕太新UC那些奇怪的瀏覽器不支援

沒錯公司要求產品要支援UC,講到這裡我想花一點篇幅抱怨一下,建議看到這裡的工程師,珍惜生命,遠離UC,下次面試記得要問主管公司有沒有要求要支援UC,有人說UC就像手幾板的ie我覺得他說錯了,他比ie還不如!不僅沒辦法debug,之前發生一次白屏,在我一行一行除錯,最後問題竟然是html排序它不滿意,不是什麼沒有加上</>這種低級錯誤喔,就是單純排序它不滿意,當時的狀況就類似以下:
<div>
    hello
    <div>hello2</div>
<div>
然後我把它改成
<div>hello<div>
<div>hello2</div>
就正常了.....我還以為我又用什麼新的寫法或物件戳到它的G點,Fuck!這我真的沒辦法誒!!

好拉,工作還是要繼續下去,但這還在研發階段之不支援我就先不考慮,之後不支援大不了不用!所以你以為我就乖乖刻輪子嗎?當然沒有,我想到既然底層都是Proxy那我用signal就好拉,那畫面不會reactive的問題怎麼處理?奶綠茶大大的網誌有提到react18有出一個新的hook是useSyncExternalStore 這個hook可以控制畫面要不要渲染,網誌裡面有提到大大如何包裝這個function,我試著去改寫但我一直嘗試不成功,因為我一直把變數放在function內,所以在rerender時function會重跑一遍,所以變數每次都會被重置(Fuck我就說寫法很不直覺吧),當我想到這問題時已經好幾天過去了,如果不想變數被重置,那就使用原生的hook來解決吧,最後產出來的code就如下

沒錯,前面說這麼多我只是想要抱怨,最終結果在下面,大家如果為了自己身體著想,可以直接拿去使用

我模擬出一些比較常用的hook,其他我也很少用就不做了,畢竟這就像那些砲轟我的網友說的,就是vue皮react骨,我也沒有不承認這件事,但更新資料畫面就會自動更新就是爽,也不用在那邊setState後還要在useEffect等狀態更新才能在function裡面執行我想執行的內容,這寫法真的很蠢......抱歉我不該戰,這寫法真的很不直覺

好吧廢話不多說直接上程式碼

import { useReactive } from "@reactivedata/react"
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react"
import { signal, effect, computed as preactComputed } from "@preact/signals-core"

export const onMounted = (fn) => {
useEffect(() => {
fn()
}, [])
}
export const onUnmounted = (fn) => {
useEffect(() => {
return () => {
fn()
}
}, [])
}

export const ref = (initValue) => {
const valueRef = useRef(initValue)
const versionRef = useRef(0)
function createStore(signal) {
let onChangeNotifyReact
const unsubscribe = effect(() => {
valueRef.current = signal.value
versionRef.current++
onChangeNotifyReact?.()
})
return {
subscribe(listener) {
onChangeNotifyReact = listener
return () => {
unsubscribe()
}
},
getSnapshop() {
return versionRef.current
},
}
}
const res = signal(valueRef.current)
const store = useMemo(() => {
return createStore(res)
}, [res])
useSyncExternalStore(store.subscribe, store.getSnapshop)
return res
// const _value = useRef(initValue)
// const [ref, setRef] = useState({ value: initValue })
// Object.defineProperty(ref, "value", {
// get() {
// return _value.current
// },
// set(newValue) {
// _value.current = newValue
// setRef({ value: newValue })
// },
// })
// return ref
}
export const reactive = useReactive
export const computed = (computeFn) => {
const res = preactComputed(computeFn)
function createStore(res) {
let onChangeNotifyReact
const unsubscribe = effect(() => {
res.value
onChangeNotifyReact?.()
})
return {
subscribe(listener) {
onChangeNotifyReact = listener
return () => {
unsubscribe()
}
},
getSnapshop() {
return res.value
},
}
}
const store = useMemo(() => {
return createStore(res)
}, [res])
useSyncExternalStore(store.subscribe, store.getSnapshop)
return res.value
}

export const watch = (sSatcher, fn) => {
const watcher = typeof sSatcher === "object" ? sSatcher : [sSatcher]
const value = useMemo(
() =>
watcher.map((x) => {
return typeof x == "function" ? x() : x
}),
[watcher]
)
const ref = useRef(value)
if (value.toString() !== ref.current.toString()) {
ref.current = value
}
useEffect(fn, ref.current)
}

export { effect }

這樣onMounted, onUnmounted也解決了useEffect裡面不能放async的function,那警告看了真的很礙眼,支援就不要吵,不支援就直接抱錯,在那邊吵什麼吵,然後為什麼不多包一層就好,我也不知道,可能礙於什麼東方神秘力量

reactive就直接用別人寫好的吧

watch就是watch

computed這麼好用的功能怎麼能不實現(但我沒實現getter、setter,沒錯因為我不常用)

ref就用useRef去解決變數被重置問題,我們來比較一下差別
// normal way
const [value, setValue] = useState(0)
setValue(1)
console.log(value) // 0 wtf!
useEffect(() => {
    console.log(value) // 1
}, [value])

// better way
const [value, setValue] = useState(0)
const newValue = 1
setValue(newValue)
console.log(newValue) // 1 .....why so troublesome?

// new way
const state = ref(0)
state.value++
console.log(state.value) // 1
// 484很直覺!484很爽!484很好寫


後記

最重要UC, QQ這些中國大牌瀏覽器到底有沒有支援Proxy呢~~答案是有的,至少上線到現在沒有傳出什麼災情
謎之聲:連Promise.race跟any都可以拔掉,竟然支援Proxy,真是可喜可賀~
不過想想也是拉,現在vue3都用Proxy寫了,如此泱泱大國總不會想跟自國工程師對著幹吧!

最後,我沒有實際測過使用起來的效能,我相信應該不會太好,畢竟有內容更新畫面就要重新渲染一次,原本react18 queue的優化都沒用了,但我才不管,開發體驗最重要!!事實上,你如果注重效能就不會選擇react框架了,那這篇就先到這裡拉,希望大家都可以準時下班,see u later~

12/18更新

computed並非是及時的,useSyncExternalStore只用來更新畫面,所以如果要用在邏輯演算中,用useMemo效果可能比較好,之後再來思考看有沒有機會debug,下面有個使用範例,這真令人有點難過

const users = [{ birthday: 2020-01-01}, {birthday:"2022-02-02"}]

let index = 0

const user = computed(() => users[index].birthday) 

console.log(user.birthday) // 2020-01-01

index = 1

console.log(user.birthday) // 2020-01-01 來不及更新