🔍 搜尋結果:"

🔍 搜尋結果:"

🔥 大幅提升你的 NextJS 能力:嘗試手寫一個 GitHub 星星監視器 🤯

在本文中,您將學習如何建立 **GitHub 星數監視器** 來檢查您幾個月內的星數以及每天獲得的星數。 - 使用 GitHub API 取得目前每天收到的星星數量。 - 在螢幕上每天繪製美麗的星星圖表。 - 創造一個工作來每天收集新星星。 ![吉米](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n524rmr0gpgr79p4qlhj.gif) --- ## 你的後台工作平台🔌 [Trigger.dev](https://trigger.dev/) 是一個開源程式庫,可讓您使用 NextJS、Remix、Astro 等為您的應用程式建立和監控長時間執行的作業!   [![GiveUsStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm9mrmovmn26izyik95z.gif)](https://github.com/triggerdotdev/trigger.dev) 請幫我們一顆星🥹。 這將幫助我們建立更多這樣的文章💖 https://github.com/triggerdotdev/trigger.dev --- ## 這是你需要知道的 😻 取得 GitHub 上星星數量的大部分工作將透過 GitHub API 完成。 GitHub API 有一些限制: - 每個請求最多 100 名觀星者 - 最多 100 個同時請求 - 每小時最多 60 個請求 [TriggerDev](https://github.com/triggerdotdev/trigger.dev) 儲存庫擁有超過 5000 顆星,實際上不可能在合理的時間內(即時)計算所有星數。 因此,我們將採用與 [GitHub Stars History](https://star-history.com/) 相同的技巧。 - 取得星星總數 (**5,715**) 除以每頁 **100** 結果 = **58 頁** - 設定我們想要的最大請求量(**20 頁最大**)除以 **58 頁** = 跳過 3 頁。 - 從這些頁面中獲取星星**(2000 顆星)**,然後獲取剩餘的星星,我們將按比例加入到其他日期(**3715 顆星**)。 它會為我們繪製一個漂亮的圖表,並在需要的地方用星星凸起。 當我們每天獲取新數量的星星時,事情就會變得容易得多。 我們將用目前擁有的星星總數減去 GitHub 上的新星星數量。 **我們不再需要迭代觀星者。** --- ## 讓我們來設定一下 🔥 我們的申請將包含一頁: - 新增您想要監控的儲存庫。 - 查看儲存庫清單及其 GitHub 星圖。 - 刪除那些你不再想要的。 ![StarsOverTime](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbii15mn1tyuz63kjphk.png) > 💡 我們將使用 NextJS 新的應用程式路由器,在安裝專案之前請確保您的節點版本為 18+。 > 使用 NextJS 設定一個新專案 ``` npx create-next-app@latest ``` 我們必須將所有星星保存到我們的資料庫中! 在我們的示範中,我們將使用 SQLite 和 `Prisma`。 它非常容易安裝,但可以隨意使用任何其他資料庫。 ``` npm install prisma @prisma/client --save ``` 在我們的專案中安裝 Prisma ``` npx prisma init --datasource-provider sqlite ``` 轉到“prisma/schema.prisma”並將其替換為以下模式: ``` generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = env("DATABASE_URL") } model Repository { id String @id @default(uuid()) month Int year Int day Int name String stars Int @@unique([name, day, month, year]) } ``` 然後執行 ``` npx prisma db push ``` 我們基本上已經在 SQLite 資料庫中建立了一個名為「Repository」的新表: - 「月」、「年」、「日」是日期。 - `name` 儲存庫的名稱 - 「星星」以及該特定日期的星星數量。 你還可以看到我們在底部加入了一個`@@unique`,這意味著我們可以將`name`,`month`,`year`,`day`一起重複記錄。它會拋出一個錯誤。 讓我們新增 Prisma 客戶端。 建立一個名為「helper」的新資料夾,並新增一個名為「prisma.ts」的新文件,並在其中新增以下程式碼: ``` import {PrismaClient} from '@prisma/client'; export const prisma = new PrismaClient(); ``` 我們稍後可以使用該「prisma」變數來查詢我們的資料庫。 --- ## 應用程式 UI 骨架 💀 我們需要一些函式庫來完成本教學: - **Axios** - 向伺服器發送請求(如果您覺得更舒服,可以隨意使用 fetch) - **Dayjs -** 很棒的處理日期的函式庫。它是 moment.js 的替代品,但不再完全維護。 - **Lodash -** 很酷的資料結構庫。 - **react-hook-form -** 處理表單的最佳函式庫(驗證/值/等) - **chart.js** - 我選擇繪製 GitHub 星圖的函式庫。 讓我們安裝它們: ``` npm install axios dayjs lodash @types/lodash chart.js react-hook-form react-chartjs-2 --save ``` 建立一個名為“components”的新資料夾並新增一個名為“main.tsx”的新文件 新增以下程式碼: ``` "use client"; import {useForm} from "react-hook-form"; import axios from "axios"; import {Repository} from "@prisma/client"; import {useCallback, useState} from "react"; export default function Main() { const [repositoryState, setRepositoryState] = useState([]); const {register, handleSubmit} = useForm(); const submit = useCallback(async (data: any) => { const {data: repositoryResponse} = await axios.post('/api/repository', {todo: 'add', repository: data.name}); setRepositoryState([...repositoryState, ...repositoryResponse]); }, [repositoryState]) const deleteFromList = useCallback((val: List) => () => { axios.post('/api/repository', {todo: 'delete', repository: `https://github.com/${val.name}`}); setRepositoryState(repositoryState.filter(v => v.name !== val.name)); }, [repositoryState]) return ( <div className="w-full max-w-2xl mx-auto p-6 space-y-12"> <form className="flex items-center space-x-4" onSubmit={handleSubmit(submit)}> <input className="flex-grow p-3 border border-black/20 rounded-xl" placeholder="Add Git repository" type="text" {...register('name', {required: 'true'})} /> <button className="flex-shrink p-3 border border-black/20 rounded-xl" type="submit"> Add </button> </form> <div className="divide-y-2 divide-gray-300"> {repositoryState.map(val => ( <div key={val.name} className="space-y-4"> <div className="flex justify-between items-center py-10"> <h2 className="text-xl font-bold">{val.name}</h2> <button className="p-3 border border-black/20 rounded-xl bg-red-400" onClick={deleteFromList(val)}>Delete</button> </div> <div className="bg-white rounded-lg border p-10"> <div className="h-[300px]]"> {/* Charts Component */} </div> </div> </div> ))} </div> </div> ) } ``` **超簡單的React元件** - 允許我們新增新的 GitHub 庫並將其發送到伺服器 POST 的表單 - `/api/repository` `{todo: 'add'}` - 刪除我們不需要 POST 的儲存庫 - `/api/repository` `{todo: 'delete'}` - 所有新增的庫及其圖表的清單。 讓我們轉到本文的複雜部分,新增儲存庫。 --- ## 數星星 ![CountingStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4m2j6046myxwv2c8kwla.gif) 在「helper」內部建立一個名為「all.stars.ts」的新檔案並新增以下程式碼: ``` import axios from "axios"; import dayjs from "dayjs"; import utc from 'dayjs/plugin/utc'; dayjs.extend(utc); const requestAmount = 20; export const getAllGithubStars = async (owner: string, name: string) => { // Get the amount of stars from GitHub const totalStars = (await axios.get(`https://api.github.com/repos/${owner}/${name}`)).data.stargazers_count; // get total pages const totalPages = Math.ceil(totalStars / 100); // How many pages to skip? We don't want to spam requests const pageSkips = totalPages < requestAmount ? requestAmount : Math.ceil(totalPages / requestAmount); // Send all the requests at the same time const starsDates = (await Promise.all([...new Array(requestAmount)].map(async (_, index) => { const getPage = (index * pageSkips) || 1; return (await axios.get(`https://api.github.com/repos/${owner}/${name}/stargazers?per_page=100&page=${getPage}`, { headers: { Accept: "application/vnd.github.v3.star+json", }, })).data; }))).flatMap(p => p).reduce((acc: any, stars: any) => { const yearMonth = stars.starred_at.split('T')[0]; acc[yearMonth] = (acc[yearMonth] || 0) + 1; return acc; }, {}); // how many stars did we find from a total of `requestAmount` requests? const foundStars = Object.keys(starsDates).reduce((all, current) => all + starsDates[current], 0); // Find the earliest date const lowestMonthYear = Object.keys(starsDates).reduce((lowest, current) => { if (lowest.isAfter(dayjs.utc(current.split('T')[0]))) { return dayjs.utc(current.split('T')[0]); } return lowest; }, dayjs.utc()); // Count dates until today const splitDate = dayjs.utc().diff(lowestMonthYear, 'day') + 1; // Create an array with the amount of stars we didn't find const array = [...new Array(totalStars - foundStars)]; // Set the amount of value to add proportionally for each day let splitStars: any[][] = []; for (let i = splitDate; i > 0; i--) { splitStars.push(array.splice(0, Math.ceil(array.length / i))); } // Calculate the amount of stars for each day return [...new Array(splitDate)].map((_, index, arr) => { const yearMonthDay = lowestMonthYear.add(index, 'day').format('YYYY-MM-DD'); const value = starsDates[yearMonthDay] || 0; return { stars: value + splitStars[index].length, date: { month: +dayjs.utc(yearMonthDay).format('M'), year: +dayjs.utc(yearMonthDay).format('YYYY'), day: +dayjs.utc(yearMonthDay).format('D'), } }; }); } ``` 那麼這裡發生了什麼事: - `totalStars` - 我們計算圖書館擁有的星星總數。 - `totalPages` - 我們計算頁數 **(每頁 100 筆記錄)** - `pageSkips` - 由於我們最多需要 20 個請求,因此我們檢查每次必須跳過多少頁。 - `starsDates` - 我們填充每個日期的星星數量。 - `foundStars` - 由於我們跳過日期,我們需要計算實際找到的星星總數。 - `lowestMonthYear` - 尋找我們擁有的恆星的最早日期。 - `splitDate` - 最早的日期和今天之間有多少個日期? - `array` - 一個包含 `splitDate` 專案數量的空陣列。 - `splitStars` - 我們缺少的星星數量,需要按比例加入每個日期。 - 最終返回 - 新陣列包含自開始以來每天的星星數量。 所以,我們已經成功建立了一個每天可以給我們星星的函數。 我嘗試過這樣顯示,結果很混亂。 您可能想要顯示每個月的星星數量。 此外,您可能想要累積星星**而不是:** - 二月 - 300 顆星 - 三月 - 200 顆星 - 四月 - 400 顆星 **如果有這樣的就更好了:** - 二月 - 300 顆星 - 三月 - 500 顆星 - 四月 - 900 顆星 兩個選項都有效。 **這取決於你想展示什麼!** 因此,讓我們轉到 helper 資料夾並建立一個名為「get.list.ts」的新檔案。 這是文件的內容: ``` import {prisma} from "./prisma"; import {groupBy, sortBy} from "lodash"; import {Repository} from "@prisma/client"; function fixStars (arr: any[]): Array<{name: string, stars: number, month: number, year: number}> { return arr.map((current, index) => { return { ...current, stars: current.stars + arr.slice(index + 1, arr.length).reduce((acc, current) => acc + current.stars, 0), } }).reverse(); } export const getList = async (data?: Repository[]) => { const repo = data || await prisma.repository.findMany(); const uniqMonth = Object.values( groupBy( sortBy( Object.values( groupBy(repo, (p) => p.name + '-' + p.year + '-' + p.month)) .map(current => { const stars = current.reduce((acc, current) => acc + current.stars, 0); return { name: current[0].name, stars, month: current[0].month, year: current[0].year } }), [(p: any) => -p.year, (p: any) => -p.month] ),p => p.name) ); const fixMonthDesc = uniqMonth.map(p => fixStars(p)); return fixMonthDesc.map(p => ({ name: p[0].name, list: p })); } ``` 首先,它將所有按日的星星轉換為按月的星星。 稍後我們會累積每個月的星星數量。 這裡要注意的一件主要事情是 `data?: Repository[]` 是可選的。 我們制定了一個簡單的邏輯:如果我們不傳遞資料,它將為我們資料庫中的所有儲存庫傳遞資料。 如果我們傳遞資料,它只會對其起作用。 為什麼問? - 當我們建立一個新的儲存庫時,我們需要在將其新增至資料庫後處理特定的儲存庫資料。 - 當我們重新載入頁面時,我們需要取得所有資料。 現在,讓我們來處理我們的星星建立/刪除路線。 轉到“src/app/api”並建立一個名為“repository”的新資料夾。在該資料夾中,建立一個名為「route.tsx」的新檔案。 在那裡加入以下程式碼: ``` import {getAllGithubStars} from "../../../../helper/all.stars"; import {prisma} from "../../../../helper/prisma"; import {Repository} from "@prisma/client"; import {getList} from "../../../../helper/get.list"; export async function POST(request: Request) { const body = await request.json(); if (!body.repository) { return new Response(JSON.stringify({error: 'Repository is required'}), {status: 400}); } const {owner, name} = body.repository.match(/github.com\/(?<owner>.*)\/(?<name>.*)/).groups; if (!owner || !name) { return new Response(JSON.stringify({error: 'Repository is invalid'}), {status: 400}); } if (body.todo === 'delete') { await prisma.repository.deleteMany({ where: { name: `${owner}/${name}` } }); return new Response(JSON.stringify({deleted: true}), {status: 200}); } const starsMonth = await getAllGithubStars(owner, name); const repo: Repository[] = []; for (const stars of starsMonth) { repo.push( await prisma.repository.upsert({ where: { name_day_month_year: { name: `${owner}/${name}`, month: stars.date.month, year: stars.date.year, day: stars.date.day, }, }, update: { stars: stars.stars, }, create: { name: `${owner}/${name}`, month: stars.date.month, year: stars.date.year, day: stars.date.day, stars: stars.stars, } }) ); } return new Response(JSON.stringify(await getList(repo)), {status: 200}); } ``` 我們共享 DELETE 和 CREATE 路由,這些路由通常不應在生產中使用,但我們在本文中這樣做是為了讓您更輕鬆。 我們從請求中取得 JSON,檢查「repository」欄位是否存在,並且它是 GitHub 儲存庫的有效路徑。 如果是刪除請求,我們使用 prisma 根據儲存庫名稱從資料庫中刪除儲存庫並傳回請求。 如果是建立,我們使用 getAllGithubStars 來獲取資料以保存到我們的資料庫中。 > 💡 由於我們已經在 `name`、`month`、`year` 和 `day` 上放置了唯一索引,如果記錄已經存在,我們可以使用 `prisma` `upsert` 來更新資料 最後,我們將新累積的資料回傳給客戶端。 最困難的部分完成了🍾 --- ## 主頁人口 💽 我們還沒有建立我們的主頁元件。 **我們開始做吧。** 前往“app”資料夾建立或編輯“page.tsx”並新增以下程式碼: ``` "use server"; import Main from "@/components/main"; import {getList} from "../../helper/get.list"; export default async function Home() { const list: any[] = await getList(); return ( <Main list={list} /> ) } ``` 我們使用與 getList 相同的函數來取得累積的所有儲存庫的所有資料。 我們還修改主要元件以支援它。 編輯 `components/main.tsx` 並將其替換為: ``` "use client"; import {useForm} from "react-hook-form"; import axios from "axios"; import {Repository} from "@prisma/client"; import {useCallback, useState} from "react"; interface List { name: string, list: Repository[] } export default function Main({list}: {list: List[]}) { const [repositoryState, setRepositoryState] = useState(list); const {register, handleSubmit} = useForm(); const submit = useCallback(async (data: any) => { const {data: repositoryResponse} = await axios.post('/api/repository', {todo: 'add', repository: data.name}); setRepositoryState([...repositoryState, ...repositoryResponse]); }, [repositoryState]) const deleteFromList = useCallback((val: List) => () => { axios.post('/api/repository', {todo: 'delete', repository: `https://github.com/${val.name}`}); setRepositoryState(repositoryState.filter(v => v.name !== val.name)); }, [repositoryState]) return ( <div className="w-full max-w-2xl mx-auto p-6 space-y-12"> <form className="flex items-center space-x-4" onSubmit={handleSubmit(submit)}> <input className="flex-grow p-3 border border-black/20 rounded-xl" placeholder="Add Git repository" type="text" {...register('name', {required: 'true'})} /> <button className="flex-shrink p-3 border border-black/20 rounded-xl" type="submit"> Add </button> </form> <div className="divide-y-2 divide-gray-300"> {repositoryState.map(val => ( <div key={val.name} className="space-y-4"> <div className="flex justify-between items-center py-10"> <h2 className="text-xl font-bold">{val.name}</h2> <button className="p-3 border border-black/20 rounded-xl bg-red-400" onClick={deleteFromList(val)}>Delete</button> </div> <div className="bg-white rounded-lg border p-10"> <div className="h-[300px]]"> {/* Charts Components */} </div> </div> </div> ))} </div> </div> ) } ``` --- ## 顯示圖表! 📈 前往“components”資料夾並新增一個名為“chart.tsx”的新檔案。 新增以下程式碼: ``` "use client"; import {Repository} from "@prisma/client"; import {useMemo} from "react"; import React from 'react'; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend, } from 'chart.js'; import { Line } from 'react-chartjs-2'; ChartJS.register( CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend ); export default function ChartComponent({repository}: {repository: Repository[]}) { const labels = useMemo(() => { return repository.map(r => `${r.year}/${r.month}`); }, [repository]); const data = useMemo(() => ({ labels, datasets: [ { label: repository[0].name, data: repository.map(p => p.stars), borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.5)', tension: 0.2, }, ], }), [repository]); return ( <Line options={{ responsive: true, }} data={data} /> ); } ``` 我們使用“chart.js”函式庫來繪製“Line”類型的圖表。 這非常簡單,因為我們在伺服器端完成了所有資料結構。 這裡需要注意的一件大事是我們「匯出預設值」我們的 ChartComponent。那是因為它使用了「Canvas」。這在伺服器端不可用,我們需要延遲載入該元件。 讓我們修改“main.tsx”: ``` "use client"; import {useForm} from "react-hook-form"; import axios from "axios"; import {Repository} from "@prisma/client"; import dynamic from "next/dynamic"; import {useCallback, useState} from "react"; const ChartComponent = dynamic(() => import('@/components/chart'), { ssr: false, }) interface List { name: string, list: Repository[] } export default function Main({list}: {list: List[]}) { const [repositoryState, setRepositoryState] = useState(list); const {register, handleSubmit} = useForm(); const submit = useCallback(async (data: any) => { const {data: repositoryResponse} = await axios.post('/api/repository', {todo: 'add', repository: data.name}); setRepositoryState([...repositoryState, ...repositoryResponse]); }, [repositoryState]) const deleteFromList = useCallback((val: List) => () => { axios.post('/api/repository', {todo: 'delete', repository: `https://github.com/${val.name}`}); setRepositoryState(repositoryState.filter(v => v.name !== val.name)); }, [repositoryState]) return ( <div className="w-full max-w-2xl mx-auto p-6 space-y-12"> <form className="flex items-center space-x-4" onSubmit={handleSubmit(submit)}> <input className="flex-grow p-3 border border-black/20 rounded-xl" placeholder="Add Git repository" type="text" {...register('name', {required: 'true'})} /> <button className="flex-shrink p-3 border border-black/20 rounded-xl" type="submit"> Add </button> </form> <div className="divide-y-2 divide-gray-300"> {repositoryState.map(val => ( <div key={val.name} className="space-y-4"> <div className="flex justify-between items-center py-10"> <h2 className="text-xl font-bold">{val.name}</h2> <button className="p-3 border border-black/20 rounded-xl bg-red-400" onClick={deleteFromList(val)}>Delete</button> </div> <div className="bg-white rounded-lg border p-10"> <div className="h-[300px]]"> <ChartComponent repository={val.list} /> </div> </div> </div> ))} </div> </div> ) } ``` 您可以看到我們使用“nextjs/dynamic”來延遲載入元件。 我希望將來 NextJS 能為客戶端元件加入類似「使用延遲載入」的內容 😺 --- ## 但是新星呢?來認識一下 Trigger.Dev! 每天加入新星星的最佳方法是執行 cron 請求來檢查新加入的星星並將其加入到我們的資料庫中。 不要使用 Vercel cron / GitHub 操作,或(上帝禁止)為此建立一個新伺服器。 我們可以使用 [Trigger.DEV](http://Trigger.DEV) 直接與我們的 NextJS 應用程式搭配使用。 那麼就讓我們來設定一下吧! 註冊 [Trigger.dev 帳號](https://trigger.dev/)。 註冊後,建立一個組織並為您的工作選擇一個專案名稱。 ![新組織](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bdnxq8o7el7t4utvgf1u.jpeg) 選擇 Next.js 作為您的框架,並按照將 Trigger.dev 新增至現有 Next.js 專案的流程進行操作。 ![NextJS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e4kt7e5r1mwg60atqfka.jpeg) 否則,請點選專案儀表板側邊欄選單上的「環境和 API 金鑰」。 ![開發金鑰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ser7a2j5qft9vw8rfk0m.png) 複製您的 DEV 伺服器 API 金鑰並執行下面的程式碼片段以安裝 Trigger.dev。 仔細按照說明進行操作。 ``` npx @trigger.dev/cli@latest init ``` 在另一個終端中執行以下程式碼片段,在 Trigger.dev 和您的 Next.js 專案之間建立隧道。 ``` npx @trigger.dev/cli@latest dev ``` 讓我們建立 TriggerDev 作業! 您將看到一個新建立的資料夾,名為“jobs”。 在那裡建立一個名為“sync.stars.ts”的新文件 新增以下程式碼: ``` import { cronTrigger, invokeTrigger } from "@trigger.dev/sdk"; import { client } from "@/trigger"; import { prisma } from "../../helper/prisma"; import axios from "axios"; import { z } from "zod"; // Your first job // This Job will be triggered by an event, log a joke to the console, and then wait 5 seconds before logging the punchline. client.defineJob({ id: "sync-stars", name: "Sync Stars Daily", version: "0.0.1", // Run a cron every day at 23:00 AM trigger: cronTrigger({ cron: "0 23 * * *", }), run: async (payload, io, ctx) => { const repos = await io.runTask("get-stars", async () => { // get all libraries and current amount of stars return await prisma.repository.groupBy({ by: ["name"], _sum: { stars: true, }, }); }); //loop through all repos and invoke the Job that gets the latest stars for (const repo of repos) { getStars.invoke(repo.name, { name: repo.name, previousStarCount: repo?._sum?.stars || 0, }); } }, }); const getStars = client.defineJob({ id: "get-latest-stars", name: "Get latest stars", version: "0.0.1", // Run a cron every day at 23:00 AM trigger: invokeTrigger({ schema: z.object({ name: z.string(), previousStarCount: z.number(), }), }), run: async (payload, io, ctx) => { const stargazers_count = await io.runTask("get-stars", async () => { const { data } = await axios.get( `https://api.github.com/repos/${payload.name}`, { headers: { authorization: `token ${process.env.TOKEN}`, }, } ); return data.stargazers_count as number; }); await prisma.repository.upsert({ where: { name_day_month_year: { name: payload.name, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(), }, }, update: { stars: stargazers_count - payload.previousStarCount, }, create: { name: payload.name, stars: stargazers_count - payload.previousStarCount, month: new Date().getMonth() + 1, year: new Date().getFullYear(), day: new Date().getDate(), }, }); }, }); ``` 我們建立了一個名為“Sync Stars Daily”的新作業,該作業將在每天下午 23:00 執行 - 它在 cron 文本中的表示為:`0 23 * * *` 我們在資料庫中取得所有目前儲存庫,按名稱將它們分組,並對星星進行求和。 由於一切都在 Vercel 無伺服器上執行,因此我們可能會在檢查所有儲存庫時遇到逾時。 為此,我們將每個儲存庫傳送到不同的作業。 我們使用“invoke”建立新作業,然後在“獲取最新的星星”中處理它們 我們迭代所有新儲存庫並獲取當前的星星數量。 我們用舊的星星數量去除新的星星數量,得到今天的星星數量。 我們使用“prisma”將其新增至資料庫。沒有比這更簡單的了! 最後一件事是編輯“jobs/index.ts”並將內容替換為: ``` export * from "./sync.stars"; ``` 你就完成了🥳 --- ## 讓我們聯絡吧! 🔌 作為開源開發者,我們邀請您加入我們的[社群](https://discord.gg/nkqV9xBYWy),以做出貢獻並與維護者互動。請隨時造訪我們的 [GitHub 儲存庫](https://github.com/triggerdotdev/trigger.dev),貢獻並建立與 Trigger.dev 相關的問題。 本教學的源程式碼可在此處取得: [https://github.com/triggerdotdev/blog/tree/main/stars-monitor](https://github.com/triggerdotdev/blog/tree/main/stars-monitor) 感謝您的閱讀! --- 原文出處:https://dev.to/triggerdotdev/take-nextjs-to-the-next-level-create-a-github-stars-monitor-130a

使用 Python 和 Pytest 自動化 API 測試

您是否想過如何使用 python 測試您的 API?在本文中,我們將學習如何使用 Python 和 pytest 框架來測試我們的 API。 對於本教程,您需要安裝 python,您可以在[此處](https://www.python.org/downloads/)下載它 --- ### 簡介: - [什麼是 Python 和 Pytest 框架](#what-is-python-and-pytest-framework) - [我們專案的設定](#configuration-of-our-project) - [使用python建立虛擬環境](#creation-of-virtual-environment-with-python) - [測試的依賴關係設定](#setup-of-dependency-for-the-tests) - [建立我們的第一個測試](#creating-our-first-test) - [將被測試的 API 的定義](#definition-of-the-api-that-will-be-tested) - [建立我們的測試](#creating-our-test) - [重建我們的測試](#refactoring-our-tests) - [產生 html 報告結果](#generate-html-report-result) - [結論](#conclusion) --- ## 什麼是 Python 和 Pytest 框架 「Python」是一種高級通用程式語言,以其簡單性和可讀性而聞名。它由 Guido van Rossum 建立,於 1991 年首次發布。 Python 的設計易於學習,並且具有乾淨簡潔的語法,這使其成為初學者和經驗豐富的程式設計師的流行選擇。 「pytest」框架可以輕鬆編寫小型、可讀的測試,並且可以擴展以支援應用程式和程式庫的複雜功能測試。 --- ## 我們專案的配置 ### 用python建立虛擬環境 在開始建立之前,我們先來了解一下什麼是Python上的虛擬環境。 Python 中的虛擬環境是一個獨立的目錄或資料夾,可讓您為專案建立和管理隔離的 Python 環境。透過環境,您可以輕鬆管理依賴項,避免與不同版本的 python 發生衝突。 虛擬環境(除其他外)是: - 用於包含支援專案(庫或應用程式)所需的特定 Python 解釋器以及軟體庫和二進位檔案。預設情況下,它們與其他虛擬環境中的軟體以及作業系統中安裝的 Python 解釋器和庫隔離。 - 包含在專案目錄中的目錄中,通常名為“venv”或“.venv”,或在許多虛擬環境的容器目錄下,例如“~/.virtualenvs”。 - 未簽入原始碼控制系統(例如 Git)。 - 被認為是一次性的 - 應該很容易刪除並從頭開始重新建立它。您沒有在環境中放置任何專案程式碼 - 不被視為可移動或可複製 - 您只需在目標位置重新建立相同的環境。 您可以在[此處](https://docs.python.org/3/library/venv.html#venv-def)閱讀有關 python 環境的更多資訊。 #### 視窗 首先,為您的專案建立一個資料夾,然後打開 cmd 並使用命令 cd 導航到該資料夾: ``` cd tests_with_python ``` 如果您不知道資料夾在哪裡,可以執行命令“ls”,您將看到資料夾列表,並且可以瀏覽它們。在我們的專案資料夾中,執行以下命令: ``` python -m venv name_of_environment ``` 您的環境名稱可以是任何人,只需記住python 區分大小寫,請查看[PEP 8 風格指南](https://peps.python.org/pep-0008/) 以了解有關Python 約定的更多資訊. 要啟動我們的環境,我們使用以下命令: ``` name_of_environment\Scripts\Activate ``` 如果一切正確,您的環境將被激活,並且在 cmd 上您將看到如下所示: ``` (name_of_environment) C:\User\tests ``` 要停用您的環境,只需執行: ``` deactivate ``` #### Linux 或 MacOS 為您的專案建立一個資料夾,然後打開 cmd 並使用命令 cd 導航到該資料夾: ``` cd tests_with_python ``` 要啟動我們的環境,我們使用以下命令: ``` source name_of_environment/bin/activate ``` 如果一切正確,您的環境將被激活,並且在 cmd 上您將看到如下所示: ``` (name_of_environment) your_user_name tests % ``` 要停用您的環境,只需執行: ``` deactivate ``` ### 設定測試的依賴關係 當我們要測試 API 時,我們需要安裝依賴項來幫助我們進行測試,首先我們將安裝「requests」函式庫來幫助我們發出請求: PS:在執行此命令之前請確保您的環境已激活 ``` pip install requests ``` 為了進行測試,我們將安裝「pytests」框架: ``` pip install pytest ``` --- ## 建立我們的第一個測試 ### 將要測試的 API 的定義 在本教程中,我們將使用返回小行星列表的 Nasa API:[Asteroids - NeoWs](https://api.nasa.gov/#donkiGST),我們將測試檢索基於小行星列表的端點在他們最接近地球的日期。 關於API: - 基本網址:`https://api.nasa.gov/neo/rest/v1/feed` - 查詢參數: |參數|類型|預設|描述| | --------|---------|--------|--------------------| |start_date|YYYY-MM-DD|無|小行星搜尋的開始日期| |end_date|YYYY-MM-DD|start_date後7天|小行星搜尋的結束日期| |api_key|字串|DEMO_KEY|用於擴展用途的 api.nasa.gov 密鑰| 在本教程中,我們將重點放在三種類型的測試: - 合約:如果 API 能夠驗證傳送的查詢參數 - 狀態:狀態程式碼是否正確 - 身份驗證:即使這個API不需要令牌,我們也可以用它來做測試 我們的場景: |方法|測試|預期結果 | | --------|--------|--------------------| |獲取 |搜尋成功 | - 傳回狀態程式碼 200<br/> 正文回應包含小行星清單| |獲取 |無需任何查詢參數即可搜尋 | - 返回狀態碼403<br/>| |獲取 |僅搜尋開始日期| - 傳回狀態程式碼 200 <br/> 主體回應包含小行星清單| |獲取 |僅搜尋結束日期| - 傳回狀態程式碼 200 <br/> 主體回應包含小行星清單| |獲取 |在有效日期範圍內搜尋| - 傳回狀態碼 200<br/> - 正文回應包含所有非空白欄位| |獲取 |當開始日期大於結束日期時進行搜尋| - 傳回狀態程式碼 400 <br/>| |獲取 |使用無效的 API 令牌進行搜尋| - 傳回狀態程式碼 403 <br/> 主體回應包含小行星清單| ### 建立我們的測試 首先,我們將建立一個名為「tests.py」的文件,我們將在該文件中編寫測試。為了幫助我們使用良好的實踐並編寫良好的自動化測試,讓我們使用 [TDD(測試驅動開發)](https://www.browserstack.com/guide/what-is-test-driven-development?psafe_param=1?keyword=&campaignid=&adgroupid=&adid=8784011037660164696&utm_source=google&utm_medium=cpc&utm_platform=paidads&utm_content=602353912717&utm_campapaidads&utm_content=602353912717&utm_campa.utm_term=+&gad_source=1&gclid=CjwKCAiAxreqBhAxEiwAfGfndN8P705lwnkvEFnCz_lueR2hnhmZXgboBQEtKTaCIRbhcb1SXOxBYhoC-WoQAD_BwwE)技術。 該技術包括: - 紅色 - 進行失敗的測試 - 綠色 - 使此測試通過 - 重構 - 重構所做的事情,刪除重複的內容 為了編寫一套好的測試,我們將使用 3A 技術: - 安排:準備上下文。 - 行動:執行我們想要示範的行動。 - 斷言:表明我們預期的結果確實發生了。 從紅色開始,使用 3A 技術,我們將編寫第一個測試「成功搜尋小行星」: ``` import pytest def test_search_asteroids_with_sucess(): # Arrange: api_key = "DEMO_KEY" #Act: response = make_request(api_key) #Assertion: assert response.status_code == 200 # Validation of status code data = response.json() # Assertion of body response content: assert len(data) > 0 assert data["element_count"] > 0 ``` - 安排:我們建立一個變數來插入 api_key,在此步驟中,您可以插入執行測試所需的任何資料。通常,在這一步驟我們會建立模擬資料。 - Act:在這一步驟中我們呼叫了負責發出請求的方法 - 斷言:我們驗證回應 `方法或類別的名稱應以 test 開頭` 若要執行我們的測試,請在命令提示字元中執行: ``` pytest test.py ``` 我們將收到一個錯誤,因為我們沒有建立執行請求的方法: ``` test.py F [100%] ====================================================================== FAILURES ====================================================================== _________________________________________________________ test_search_asteroids_with_sucess __________________________________________________________ def test_search_asteroids_with_sucess(): > response = make_request() E NameError: name 'make_request' is not defined test.py:5: NameError ============================================================== short test summary info =============================================================== FAILED test.py::test_search_asteroids_with_sucess - NameError: name 'make_request' is not defined ================================================================= 1 failed in 0.01s ================================================================== ``` 現在,讓我們建立方法來執行請求: ``` import requests def make_request(api_key): base_url = "https://api.nasa.gov/neo/rest/v1/feed/" response = requests.get(f'{base_url}?api_key={api_key}') return response ``` 現在,再次執行我們的測試: ``` ================================================================ test session starts ================================================================= platform darwin -- Python 3.11.5, pytest-7.4.3, pluggy-1.3.0 rootdir: /Users/Documents/tests_python collected 1 item test.py . [100%] ================================================================= 1 passed in 20.22s ================================================================= ``` --- ## 重構我們的測試 現在我們已經了解如何使用 pytest 建立測試以及如何建立請求,我們可以編寫其他測試並開始重構測試。我們要做的第一個重構是從測試文件中刪除請求方法。我們將建立一個名為「make_requests.py」的新文件,其中將包含我們的請求,並將我們所做的請求移至此文件: ``` import requests def make_request(api_key): base_url = "https://api.nasa.gov/neo/rest/v1/feed/" response = requests.get(f'{base_url}?api_key={api_key}') return response ``` 現在,我們需要考慮在其他測試中重複使用此方法,因為我們需要為不同的測試傳遞不同的參數。我們可以透過很多方法來做到這一點,在本教程中,我們將參數的名稱從“api_key”更改為“query_parameters”。我們這樣做是為了讓我們的方法更加靈活,我們可以一次傳遞參數進行測試: ``` import requests def make_request(query_parameters): base_url = "https://api.nasa.gov/neo/rest/v1/feed/" response = requests.get(f'{base_url}?{query_parameters}') return response ``` 之後,我們需要更改我們的測試文件。我們將導入我們建立的這個方法: ``` from make_requests import make_request ``` 為了以更好的方式組織我們的測試,並遵循 pytest 文件的建議,我們將測試移至類別「TestClass」: 再次執行我們的測試: ``` ============================= test session starts ============================== collecting ... collected 7 items test.py::TestClass::test_search_asteroids_with_sucess test.py::TestClass::test_search_asteroids_with_query_parameters_empty test.py::TestClass::test_search_asteroids_with_start_date test.py::TestClass::test_search_asteroids_with_end_date test.py::TestClass::test_search_asteroids_in_valid_range test.py::TestClass::test_search_asteroids_in_invalid_range test.py::TestClass::test_search_asteroids_in_invalid_token ============================== 7 passed in 5.85s =============================== PASSED [ 14%]PASSED [ 28%]PASSED [ 42%]PASSED [ 57%]PASSED [ 71%]PASSED [ 85%]PASSED [100%] Process finished with exit code 0 ``` ### 產生 html 報告結果 為了更好地視覺化您的測試結果,我們可以使用 pytest-html-reporter 庫產生報告 html,為此,我們首先需要安裝該套件: ``` pip install pytest-html ``` 若要產生報告,請在執行測試時新增: ``` pytest test.py --html-report=./report/report.html ``` 將產生一個包含測試結果的 .html 文件,如下所示: ![report_example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eol3v7iex84t7prmyb9c.png) ## 結論 本文是一篇教程,介紹如何開始使用 python 和 pytest 框架為 API 編寫自動化測試以及如何產生一個報告 html。 您可以在[此處](https://github.com/aliciamarianne1507/tests_python)存取本教學中使用的專案。 我希望這些內容對您有用。 如果您有任何疑問,請隨時與我聯繫! 親親,下週見💅🏼 --- 原文出處:https://dev.to/m4rri4nne/automating-your-api-tests-using-python-and-pytest-23cc

⚡️7個簡單的人工智慧產品整合(與時俱進👴🏻👨‍🔧)

## 簡介 最佳的易於建構的人工智慧產品整合清單。 這些可以為你的專案帶來魔力,所以別忘了向他們表達支持🌟 現在讓我們一起走上AI之路👨‍🌾 --- ## 1. [CopilotPortal](https://github.com/RecursivelyAI/CopilotKit):將可操作的 LLM 聊天機器人嵌入您的應用程式中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x31tl645tfa3sw5lwwzv.jpg) 應用程式中的上下文感知 LLM 聊天機器人可以回答問題並採取行動。 只需幾行程式碼即可獲得一個可用的聊天機器人,然後根據需要進行自訂和嵌入。 ``` import "@copilotkit/react-ui/styles.css"; import { CopilotProvider } from "@copilotkit/react-core"; import { CopilotSidebarUIProvider } from "@copilotkit/react-ui"; export default function App(): JSX.Element { return ( <CopilotProvider chatApiEndpoint="/api/copilotkit/chat"> <CopilotSidebarUIProvider> <YourContent /> </CopilotSidebarUIProvider> </CopilotProvider> ); } ``` https://github.com/RecursivelyAI/CopilotKit --- ## 2. [LinguiJS](https://github.com/lingui/js-lingui) - 自動且簡單的國際化 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80f1yb9etnzf3z4pk7t3.png) 簡單而強大的開源國際化函式庫。 易於整合的框架,用於建立多語言反應應用程式。 ``` import { Trans } from "@lingui/macro" function App() { return ( <Trans id="msg.docs" /* id is optional */> Read the <a href="https://lingui.dev">documentation</a> for more info. </Trans> ) } ``` https://github.com/lingui/js-lingui --- ## 3. Pezzo.ai - 可觀測性、成本和即時工程平台 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxvbgi5zkghkb0t64npw.jpeg) 用於管理 OpenAI 通話的集中平台。 優化您的提示和令牌使用。追蹤您的人工智慧使用情況。 免費且易於整合。 ``` const prompt = await pezzo.getPrompt("AnalyzeSentiment"); const response = await openai.chat.completions.create(prompt); ``` https://github.com/pezzolabs/pezzo --- ## 4. CopilotTextarea - React 應用程式中的 AI 驅動寫作 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uye8z6aac1015iiqd3lk.png) 具有 Github CopilotX 功能的任何 React `<textarea>` 的直接替代品。 自動完成、插入、編輯。 可以即時或由開發人員提前提供任何上下文。 ``` import { CopilotTextarea } from "@copilotkit/react-textarea"; import { CopilotProvider } from "@copilotkit/react-core"; // Provide context... useMakeCopilotReadable(...) // in your component... <CopilotProvider> <CopilotTextarea/> </CopilotProvider>` ``` https://github.com/RecursivelyAI/CopilotKit --- ## 5. LangChain - 將人工智慧整合到鏈中。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s87kvm5jt5wmsv702r1.png) 易於使用的 API 和函式庫,用於將 LLM 新增到應用程式中。 將不同的人工智慧元件和模型連接在一起。 輕鬆嵌入上下文和語義資料以實現強大的整合。 https://github.com/langchain-ai/langchain --- ## 6. SwirlSearch - 人工智慧驅動的搜尋。 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/extnr9oxhubs6m9x817a.png) LLM 支援的搜尋、摘要和輸出。 同時搜尋多個內容來源並產生整合輸出。 功能強大,可自訂各種資料來源的應用程式內整合。 https://github.com/swirlai/swirl-search --- ## 7. ReactAgent - 用於從使用者故事產生 React 元件的實驗性 LLM 代理 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2gbb71oqobdeuh1pgnp.jpg) 使用 GPT-4 將使用者故事轉化為可用的 React 元件的實驗工具。 為其提供本地設計以實現一致的輸出和設計語言。 https://github.com/eylonmiz/react-agent --- 感謝大家! --- 原文出處:https://dev.to/copilotkit/7-easy-ai-product-integrations-to-keep-up-with-the-times--1cg2

💨 將 Javascript 應用部署到 Kubernetes 的最快方法 🌬️ ✨

## 簡介 在本教程中,您將學習如何在 Kubernetes(容器編排平台)上部署您的第一個 JavaScript 應用程式☸️。 我們將部署一個簡單的 **express** 伺服器,該伺服器使用 **Minikube** ✨ 在本機 Kubernetes 上傳回範例 JSON 物件。 **先決條件📜:** - **Docker**:用於容器化應用程式。 🐋 - **Minikube**:用於在本地執行 Kubernetes。 ☸️ ![GetADeploy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mn162frk9shm0d76z99n.gif) *** ## Odigos - 開源分散式追蹤 **無需編寫任何程式碼即可同時監控所有應用程式!** 利用唯一可以在所有應用程式中產生分散式追蹤的平台來簡化 OpenTelemetry 的複雜性。 我們真的才剛開始。 可以幫我們加個星星嗎?請問? 😽 https://github.com/keyval-dev/odigos [![貓咪](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/84twzafd93w3a4ktqflm.png)](https://github.com/keyval-dev/odigos) --- ### 讓我們設定一下🚀 我們將首先初始化我們的專案: ``` npm init -y ``` 這會使用 `package.json` 📝 檔案初始化 **NodeJS** 專案,該檔案追蹤我們安裝的依賴項。 安裝 Express.js 框架 ``` npm install express ``` 現在,在 `package.json` 中,依賴項物件應該如下所示。 ✅ ``` "dependencies": { "express": "^4.18.2" } ``` 現在,在專案的根目錄中建立一個「index.js」檔案並新增以下程式碼行。 🚀 ``` // 👇🏻 Initialize express. const express = require("express"); const app = express(); const port = 3000; // 👇🏻 Return a sample JSON object with a message property on the root path. app.get("/", (req, res) => { res.json({ message: "Hello from Odigos!", }); }); // 👇🏻 Listen on port 3000. app.listen(port, () => { console.log(`Server is listening on port ${port}`); }); ``` 我們需要在「package.json」中新增一個腳本來執行應用程式。將其新增至 `package.json` 的腳本物件中。 ``` "scripts": { "dev": "node index.js" }, ``` 現在,要檢查我們的應用程式是否正常執行,請使用「npm run dev」執行伺服器,並透過 CLI 或在瀏覽器中向「localhost:3000」發出 get 請求。 ✨ 如果您使用 CLI,請確保已安裝了 [cURL](https://curl.se/)。 ✅ ``` curl http://localhost:3000 ``` 你應該看到這樣的東西。 👇🏻 ![cURL 回應](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxs2uu8u0aa7kw6k9ta4.png) 現在,您可以使用「Ctrl + C」簡單地停止正在執行的 Express 伺服器🚫 我們的範例應用程式已準備就緒! 🎉 現在,讓我們將其容器化並推送到 Kubernetes。 🐳☸️ *** ### 將應用程式容器化📦 我們將使用 **Docker** 來容器化我們的應用程式。 在專案的根目錄中,建立一個名為「Dockerfile」的新檔案。 > 💡 確保名稱完全相同。否則,您將需要明確傳遞“-f”標誌來指定“Dockerfile”路徑。 ``` # Uses node as the base image FROM node:21-alpine # Sets up our working directory as /app inside the container. WORKDIR /app # Copyies package json files. COPY package.json package-lock.json ./ # Installs the dependencies from the package.json RUN npm install --production # Copies current directory files into the docker environment COPY . . # Expose port 3000 as our server uses it. EXPOSE 3000 # Finally runs the server. CMD ["node", "index.js"] ``` 現在,我們需要建置 ⚒️ 這個容器才能實際使用它並將其推送到 Kubernetes。 執行此命令來建置“Dockerfile”。 > 🚨 如果您在 Windows 上執行它,請確保 Docker Desktop 正在執行。 ``` // 👇🏻 We are tagging our image name to express-server docker build -t express-server . ``` 現在,是時候執行容器了。 🏃🏻‍♂️💨 ``` docker run -dp 127.0.0.1:3000:3000 express-server ``` > 💡 我們正在後台執行容器,容器連接埠 3000 對應到我們的電腦連接埠 3000。 再次執行以下命令,您應該會看到與之前相同的結果。 ✅ ``` curl http://localhost:3000 ``` > **注意**:這次應用程式沒有像以前一樣在我們的電腦上執行。相反,它在容器內運作。 🤯 *** ### 在 Kubernetes 中部署 ## 如前所述,我們將使用 Minikube 在本機電腦上建立編排環境,並使用 kubectl 命令與 Kubernetes 互動。 😄 **啟動 Minikube:🚀** ``` minikube start ``` 由於我們將使用本機容器而不是從 docker hub 中提取它們,因此請執行這些命令。 ✨ ``` eval $(minikube docker-env) docker build -t express-server . ``` `eval $(minikube docker-env)`:用於將終端機的 `docker-cli` 指向 minikube 內的 Docker 引擎。 > 🚨 注意,我們很多人都使用 Fish 作為 shell,因此對於 Fish 來說,相應的命令是 `eval (minikube docker-env)` 現在,在專案根目錄中,建立一個嵌套資料夾“k8/deployment”,並在部署資料夾中建立一個名為“deployment.yaml”的新文件,其中包含以下內容。 在此文件中,我們將管理容器的部署。 👇🏻 ``` # 👇🏻 /k8/deployment/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: express-deployment spec: selector: matchLabels: app: express-svr template: metadata: labels: app: express-svr spec: containers: - name: express-svr image: express-server imagePullPolicy: Never # Make sure to set it to Never, or else it will pull from the docker hub and fail. resources: limits: memory: "128Mi" cpu: "500m" ports: - containerPort: 3000 ``` 最後,執行此命令以應用我們剛剛建立的部署配置「deployment.yaml」。 ✨ ``` kubectl apply -f .\k8\deployment\deployment.yaml ``` 現在,如果我們查看正在執行的 Pod,我們可以看到 Pod 已成功建立。 🎉 ![執行 kubernetes pod](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83ijo09xpd9ccv30h6ug.png) 要查看我們建立的 Pod 的日誌,請執行“kubectl messages <pod_name>”,我們應該會看到以下內容。 ![正在執行的 kubernetes pod 的日誌](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f97aap8qioqsjr45rafw.png) 至此,我們的「express-server」就成功部署在本地 Kubernetes 上了。 😎 *** 這就是本文的內容,我們成功地將應用程式容器化並將其部署到 Kubernetes。 本文的原始碼可以在這裡找到 https://github.com/keyval-dev/blog/tree/main/js-on-k8s 非常感謝您的閱讀! 🎉🫡 --- 原文出處:https://dev.to/odigos/the-fastest-way-to-deploy-your-javascript-app-to-kubernetes-2j33

【Python 🐍精通】Python Linked List 及基本 Linked List 操作🛠️

在上一篇文章中,我們了解了物件導向程式設計並對 Python 的 Magic/Dunder 方法進行了全面概述。 **Python 中的物件導向程式設計 (OOP)**:Python 中的這種範例圍繞著建立可重複使用程式碼。它涉及使用類別作為物件的藍圖。這些物件可以具有定義其行為和交互的屬性(資料)和方法(函數)。 **Python 的 Magic/Dunder 方法**:Python 中的 Magic 或 Dunder(雙底線)方法是名稱以雙底線開頭和結尾的特殊方法(例如,`__init__`、`__str__`、`__repr__`)。 您可以在這裡閱讀相關內容。 👇 https://dev.to/swirl/python-mastery-pythons-object-oriented-programming-overview-and-fundamentals-22m1 今天,我們將對其進行擴展,並使用物件導向程式設計的知識來理解和建立 Python 中的鍊錶。並會對其執行一些操作。 ## 開源 Python 專案:[Swirl](https://github.com/swirlai/swirl-search) [![旋流搜尋](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9icq3q8vmtzrse1cbqcj.gif)](https://github.com/swirlai/swirl-search) 如果您對 Python 感興趣,您將會💖 [**Swirl**](https://github.com/swirlai/swirl-search)。 Swirl 是一個開源搜尋平台,它將為您提供以下知識: - Python - 人工智慧 - 在任何產品中整合大型語言模型 - 了解如何開發搜尋平台。 檢查我們的 GitHub 儲存庫: https://github.com/swirlai/swirl-search 如果您能夠:我們將非常高興: https://github.com/swirlai/swirl-search ## Linked List 連結列表是物件有序的集合。它是一種資料結構,旨在將資料保存在不連續的記憶體區塊中。 與使用連續記憶體區塊的陣列或傳統列表不同,鍊錶儲存在非連續記憶體位置。這種設計允許高效的插入和刪除,而無需重新排列整個資料結構。 這種設計允許高效的插入和刪除,而不需要重新排列整個資料結構。 ### 基本鍊錶 基本鍊錶是一種線性資料結構,其中每個元素(稱為節點)包含兩部分:資料和對清單中下一個節點的引用。這種結構允許有效地插入和刪除元素,因為它不需要移動元素,這與陣列不同。 典型的節點設計: **資料**:包含資料,可以是數字、地址、文字等。 **Next**:指向下一個資料節點或儲存下一個資料節點的位址。 ![Python 中鍊錶的節點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cus930t03hko9x0xeac2.png) 第一個節點稱為列表的頭,最後一個節點指向 None(在 Python 中)(或在其他語言中為 Null),稱為尾節點。 當你把很多節點收集在一起時,它就變成了一個鍊錶。 ![Python 中的鍊錶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ndkxz3274ylfqva4y2kh.png) ### 鍊錶的好處和操作的時間複雜度 連結清單有很多好處,特別是在動態資料操作場景中。以下是一些主要優勢: 1. **動態大小**:與陣列不同,鍊錶可以動態增長或縮小大小,這對於記憶體使用來說是高效的。 2. **易於插入/刪除**:插入或刪除節點相對簡單,因為它通常只涉及更改一些引用,而不需要像陣列中那樣移動元素。 3. **靈活性**:它們可以實現其他資料結構,如堆疊、佇列和圖鄰接表。 |運營|時間複雜度| |----------------|-----------------| |存取 | O(n) | |搜尋 | O(n) | |插入| O(1) | O(1) | |刪除 | O(1) | O(1) | _注意_:我們考慮的是單鍊錶。 ## 在 Python 中實作鍊錶。 這是將在 Python 中建立節點的程式碼。如果您對 `__repr__` 方法感到困惑,請注意。請查看本系列中的[上一篇文章](https://dev.to/swirl/python-mastery-pythons-object-oriented-programming-overview-and-fundamentals-22m1)。 ``` class Node: def __init__(self, data): self.data = data self.next = None def __repr__(self): return f"Node({self.data})" ``` 連結列表類別的程式碼。這利用了 Node 類別來建立資料並將它們連接在一起。 ``` class LinkedList: def __init__(self): self.head = None def append(self, data): new_node = Node(data) if self.head is None: self.head = new_node return last_node = self.head while last_node.next: last_node = last_node.next last_node.next = new_node def __repr__(self): nodes = [] current = self.head while current: nodes.append(repr(current)) current = current.next return "->".join(nodes) ``` 這段程式碼做了兩件事: 1. **追加**:在鍊錶末端追加一個節點。 2. `__repr__` :此方法遍歷鍊錶並以Pythonic方式列印它。 1. 這也可以使用稱為 traverse 的方法來完成。 _這是呼叫「__repr__」方法的「print(llist)」的輸出: ![在 Python 中列印連結清單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8krtl50nkifa5nhvsh4j.png) ### 遍歷鍊錶。 遍歷鍊錶就是遍歷每個節點並列印它的過程。 ``` def traverse(linked_list): current = linked_list.head while current: print(current.data) current = current.next llist = LinkedList() llist.append(1) llist.append(2) llist.append(3) print("Traversing the linked list:") traverse(llist) ``` ![Python中遍歷鍊錶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzzy5pwzixnwpgmfqynw.png) ## 反轉鍊錶 這個想法是迭代鍊錶,並且對於每個節點,將其“下一個”指標切換為指向前一個節點而不是下一個節點。這將幫助我們反轉鍊錶。 ``` def reverse_linked_list(head): previous = None current = head while current: next_node = current.next current.next = previous previous = current current = next_node return previous llist = LinkedList() llist.append(1) llist.append(2) llist.append(3) print("Original List:", llist) new_head = reverse_linked_list(llist.head) llist.head = new_head print("Reversed List:", llist) ``` ![反轉連結清單](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s05ouz9eegzukkkuofw8.png) ## 在連結清單中插入值 我們已經有一個追加函數,可以將值加到鍊錶的末尾。但是,如果我們想要一個在特定位置加入的方法,並且如果該位置不存在,則將值附加到末尾,該怎麼辦? ``` class LinkedList: def insert_after_value(self, data_after, data_to_insert): if self.head is None: return current = self.head while current: if current.data == data_after: new_node = Node(data_to_insert) new_node.next = current.next current.next = new_node return current = current.next self.append(data_to_insert) ``` ## 刪除鍊錶中的節點 若要從鍊錶中刪除節點,請建立一個函數,該函數將鍊錶的頭和要刪除的節點的資料作為參數。並遍歷鍊錶,直到找到資料,然後將其刪除。 ``` class LinkedList: def delete_node(self, data): current = self.head if current is None or current.data == data: self.head = current.next if current else None return while current.next: if current.next.data == data: current.next = current.next.next return current = current.next ``` ![刪除連結清單中的節點](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/12hjdfchdy0ofrjyyt10.png) 感謝您閱讀本文。在本系列的後續文章中,我們將討論 Python 和 Python 資料結構的更複雜的細節。 ## 為 [Swirl] 做出貢獻(https://github.com/swirlai/swirl-search) [![為 Swirl 做出貢獻](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itecirhtu5lghtlr6m2a.jpg)](https://github.com/swirlai/swirl-search) [Swirl](https://github.com/swirlai/swirl-search) 是一個開源 Python 專案。為 Swirl 做出貢獻可以幫助您獲得生產級的 Python 知識並提高您的技能。 檢查我們的 GitHub 儲存庫: https://github.com/swirlai/swirl-search 如果您能夠:我們將非常高興: https://github.com/swirlai/swirl-search 謝謝閱讀, 你們都令人嘆為觀止。 ![你太棒了](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqhf5vqr2lj4z5kq4gdr.gif) --- 原文出處:https://dev.to/swirl/python-mastery-overview-of-linked-list-in-python-essential-linked-list-operations-hn3

🚀使用 NextJS、Trigger.dev 和 GPT4 做一個履歷表產生器🔥✨

## 簡介 在本文中,您將學習如何使用 NextJS、Trigger.dev、Resend 和 OpenAI 建立簡歷產生器。 😲 - 加入基本詳細訊息,例如名字、姓氏和最後工作地點。 - 產生詳細訊息,例如個人資料摘要、工作經歷和工作職責。 - 建立包含所有資訊的 PDF。 - 將所有內容傳送到您的電子郵件 ![猴子手錶](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/23k6hee187s62k8y1dmd.gif) *** ## 你的後台工作平台🔌 [Trigger.dev](https://trigger.dev/) 是一個開源程式庫,可讓您使用 NextJS、Remix、Astro 等為您的應用程式建立和監控長時間執行的作業!   [![GiveUsStars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bm9mrmovmn26izyik95z.gif)](https://github.com/triggerdotdev/trigger.dev) 請幫我們一顆星🥹。 這將幫助我們建立更多這樣的文章💖 https://github.com/triggerdotdev/trigger.dev --- ## 讓我們來設定一下吧🔥 使用 NextJS 設定一個新專案 ``` npx create-next-app@latest ``` 我們將建立一個包含基本資訊的簡單表單,例如: - 名 - 姓 - 電子郵件地址 - 你的頭像 - 以及你今天為止的經驗! ![輸入](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01mmvn0lvw1p1i4knoa8.png) 我們將使用 NextJS 的新應用程式路由器。 開啟`layout.tsx`並加入以下程式碼 ``` import { GeistSans } from "geist/font"; import "./globals.css"; const defaultUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"; export const metadata = { metadataBase: new URL(defaultUrl), title: "Resume Builder with GPT4", description: "The fastest way to build a resume with GPT4", }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en" className={GeistSans.className}> <body className="bg-background text-foreground"> <main className="min-h-screen flex flex-col items-center"> {children} </main> </body> </html> ); } ``` 我們基本上是為所有頁面設定佈局(即使我們只有一頁。) 我們設定基本的頁面元資料、背景和全域 CSS 元素。 接下來,讓我們打開“page.tsx”並加入以下程式碼: ``` <div className="flex-1 w-full flex flex-col items-center"> <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16"> <div className="w-full max-w-6xl flex justify-between items-center p-3 text-sm"> <span className="font-bold select-none">resumeGPT.</span> </div> </nav> <div className="animate-in flex-1 flex flex-col opacity-0 max-w-6xl px-3"> <Home /> </div> </div> ``` 這設定了我們的resumeGPT 的標題和主要的家庭元件。 <小時/> ## 建立表單的最簡單方法 保存表單資訊並驗證欄位最簡單的方法是使用react-hook-form。 我們將上傳個人資料照片。 為此,我們不能使用基於 JSON 的請求。 我們需要將 JSON 轉換為有效的表單資料。 那麼就讓我們把它們全部安裝吧! ``` npm install react-hook-form object-to-formdata axios --save ``` 建立一個名為 Components 的新資料夾,新增一個名為「Home.tsx」的新文件,並新增以下程式碼: ``` "use client"; import React, { useState } from "react"; import {FormProvider, useForm} from "react-hook-form"; import Companies from "@/components/Companies"; import axios from "axios"; import {serialize} from "object-to-formdata"; export type TUserDetails = { firstName: string; lastName: string; photo: string; email: string; companies: TCompany[]; }; export type TCompany = { companyName: string; position: string; workedYears: string; technologies: string; }; const Home = () => { const [finished, setFinished] = useState<boolean>(false); const methods = useForm<TUserDetails>() const { register, handleSubmit, formState: { errors }, } = methods; const handleFormSubmit = async (values: TUserDetails) => { axios.post('/api/create', serialize(values)); setFinished(true); }; if (finished) { return ( <div className="mt-10">Sent to the queue! Check your email</div> ) } return ( <div className="flex flex-col items-center justify-center p-7"> <div className="w-full py-3 bg-slate-500 items-center justify-center flex flex-col rounded-t-lg text-white"> <h1 className="font-bold text-white text-3xl">Resume Builder</h1> <p className="text-gray-300"> Generate a resume with GPT in seconds 🚀 </p> </div> <FormProvider {...methods}> <form onSubmit={handleSubmit(handleFormSubmit)} className="p-4 w-full flex flex-col" > <div className="flex flex-col lg:flex-row gap-4"> <div className="flex flex-col w-full"> <label htmlFor="firstName">First name</label> <input type="text" required id="firstName" placeholder="e.g. John" className="p-3 rounded-md outline-none border border-gray-500 text-white bg-transparent" {...register('firstName')} /> </div> <div className="flex flex-col w-full"> <label htmlFor="lastName">Last name</label> <input type="text" required id="lastName" placeholder="e.g. Doe" className="p-3 rounded-md outline-none border border-gray-500 text-white bg-transparent" {...register('lastName')} /> </div> </div> <hr className="w-full h-1 mt-3" /> <label htmlFor="email">Email Address</label> <input type="email" required id="email" placeholder="e.g. [email protected]" className="p-3 rounded-md outline-none border border-gray-500 text-white bg-transparent" {...register('email', {required: true, pattern: /^\S+@\S+$/i})} /> <hr className="w-full h-1 mt-3" /> <label htmlFor="photo">Upload your image 😎</label> <input type="file" id="photo" accept="image/x-png" className="p-3 rounded-md outline-none border border-gray-500 mb-3" {...register('photo', {required: true})} /> <Companies /> <button className="p-4 pointer outline-none bg-blue-500 border-none text-white text-base font-semibold rounded-lg"> CREATE RESUME </button> </form> </FormProvider> </div> ); }; export default Home; ``` 您可以看到我們從「使用客戶端」開始,它基本上告訴我們的元件它應該只在客戶端上執行。 為什麼我們只需要客戶端? React 狀態(輸入變更)僅在用戶端可用。 我們設定兩個接口,「TUserDetails」和「TCompany」。它們代表了我們正在使用的資料的結構。 我們將“useForm”與“react-hook-form”一起使用。它為我們的輸入建立了本地狀態管理,並允許我們輕鬆更新和驗證我們的欄位。您可以看到,在每個「輸入」中,都有一個簡單的「註冊」函數,用於指定輸入名稱和驗證並將其註冊到託管狀態。 這很酷,因為我們不需要使用像“onChange”這樣的東西 您還可以看到我們使用了“FormProvider”,這很重要,因為我們希望在子元件中擁有“react-hook-form”的上下文。 我們還有一個名為「handleFormSubmit」的方法。這是我們提交表單後呼叫的方法。您可以看到我們使用“serialize”函數將 javascript 物件轉換為 FormData,並向伺服器發送請求以使用“axios”啟動作業。 您可以看到另一個名為“Companies”的元件。該元件將讓我們指定我們工作過的所有公司。 那麼讓我們努力吧。 建立一個名為「Companies.tsx」的新文件 並加入以下程式碼: ``` import React, {useCallback, useEffect} from "react"; import { TCompany } from "./Home"; import {useFieldArray, useFormContext} from "react-hook-form"; const Companies = () => { const {control, register} = We(); const {fields: companies, append} = useFieldArray({ control, name: "companies", }); const addCompany = useCallback(() => { append({ companyName: '', position: '', workedYears: '', technologies: '' }) }, [companies]); useEffect(() => { addCompany(); }, []); return ( <div className="mb-4"> {companies.length > 1 ? ( <h3 className="font-bold text-white text-3xl my-3"> Your list of Companies: </h3> ) : null} {companies.length > 1 && companies.slice(1).map((company, index) => ( <div key={index} className="mb-4 p-4 border bg-gray-800 rounded-lg shadow-md" > <div className="mb-2"> <label htmlFor={`companyName-${index}`} className="text-white"> Company Name </label> <input type="text" id={`companyName-${index}`} className="p-2 border border-gray-300 rounded-md w-full bg-transparent" {...register(`companies.${index}.companyName`, {required: true})} /> </div> <div className="mb-2"> <label htmlFor={`position-${index}`} className="text-white"> Position </label> <input type="text" id={`position-${index}`} className="p-2 border border-gray-300 rounded-md w-full bg-transparent" {...register(`companies.${index}.position`, {required: true})} /> </div> <div className="mb-2"> <label htmlFor={`workedYears-${index}`} className="text-white"> Worked Years </label> <input type="number" id={`workedYears-${index}`} className="p-2 border border-gray-300 rounded-md w-full bg-transparent" {...register(`companies.${index}.workedYears`, {required: true})} /> </div> <div className="mb-2"> <label htmlFor={`workedYears-${index}`} className="text-white"> Technologies </label> <input type="text" id={`technologies-${index}`} className="p-2 border border-gray-300 rounded-md w-full bg-transparent" {...register(`companies.${index}.technologies`, {required: true})} /> </div> </div> ))} <button type="button" onClick={addCompany} className="mb-4 p-2 pointer outline-none bg-blue-900 w-full border-none text-white text-base font-semibold rounded-lg"> Add Company </button> </div> ); }; export default Companies; ``` 我們從 useFormContext 開始,它允許我們取得父元件的上下文。 接下來,我們使用 useFieldArray 建立一個名為 Companies 的新狀態。這是我們擁有的所有公司的一個陣列。 在「useEffect」中,我們新增陣列的第一項以對其進行迭代。 當點擊“addCompany”時,它會將另一個元素推送到陣列中。 我們已經和客戶完成了🥳 --- ## 解析HTTP請求 還記得我們向“/api/create”發送了一個“POST”請求嗎? 讓我們轉到 app/api 資料夾並在該資料夾中建立一個名為「create」的新資料夾,建立一個名為「route.tsx」的新檔案並貼上以下程式碼: ``` import {NextRequest, NextResponse} from "next/server"; import {client} from "@/trigger"; export async function POST(req: NextRequest) { const data = await req.formData(); const allArr = { name: data.getAll('companies[][companyName]'), position: data.getAll('companies[][position]'), workedYears: data.getAll('companies[][workedYears]'), technologies: data.getAll('companies[][technologies]'), }; const payload = { firstName: data.get('firstName'), lastName: data.get('lastName'), photo: Buffer.from((await (data.get('photo[0]') as File).arrayBuffer())).toString('base64'), email: data.get('email'), companies: allArr.name.map((name, index) => ({ companyName: allArr.name[index], position: allArr.position[index], workedYears: allArr.workedYears[index], technologies: allArr.technologies[index], })).filter((company) => company.companyName && company.position && company.workedYears && company.technologies) } await client.sendEvent({ name: 'create.resume', payload }); return NextResponse.json({ }) } ``` > 此程式碼只能在 NodeJS 版本 20+ 上運作。如果版本較低,將無法解析FormData。 該程式碼非常簡單。 - 我們使用 `req.formData` 將請求解析為 FormData - 我們將基於 FormData 的請求轉換為 JSON 檔案。 - 我們提取圖像並將其轉換為“base64” - 我們將所有內容傳送給 TriggerDev --- ## 製作履歷並將其發送到您的電子郵件📨 建立履歷是我們需要的長期任務 - 使用 ChatGPT 產生內容。 - 建立 PDF - 發送到您的電子郵件 由於某些原因,我們不想發出長時間執行的 HTTP 請求來執行所有這些操作。 1. 部署到 Vercel 時,無伺服器功能有 10 秒的限制。我們永遠不會準時到達。 2.我們希望讓用戶不會長時間掛起。這是一個糟糕的使用者體驗。如果用戶關閉窗口,整個過程將失敗。 ### 介紹 Trigger.dev! 使用 Trigger.dev,您可以在 NextJS 應用程式內執行後台進程!您不需要建立新伺服器。 他們也知道如何透過將長時間執行的作業無縫地分解為短期任務來處理它們。 註冊 [Trigger.dev 帳號](https://trigger.dev/)。註冊後,建立一個組織並為您的工作選擇一個專案名稱。 ![CreateOrg](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shf1jsb4gio1zrjtz91d.jpeg) 選擇 Next.js 作為您的框架,並按照將 Trigger.dev 新增至現有 Next.js 專案的流程進行操作。 ![下一頁](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5guppb6rot13myu6th5c.jpeg) 否則,請點選專案儀表板側邊欄選單上的「環境和 API 金鑰」。 ![複製](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x5gh527u7sthp6clkcfa.png) 複製您的 DEV 伺服器 API 金鑰並執行下面的程式碼片段以安裝 Trigger.dev。仔細按照說明進行操作。 ``` npx @trigger.dev/cli@latest init ``` 在另一個終端中,執行以下程式碼片段以在 Trigger.dev 和 Next.js 專案之間建立隧道。 ``` npx @trigger.dev/cli@latest dev ``` 讓我們建立 TriggerDev 作業! 前往新建立的資料夾 jobs 並建立一個名為「create.resume.ts」的新檔案。 新增以下程式碼: ``` client.defineJob({ id: "create-resume", name: "Create Resume", version: "0.0.1", trigger: eventTrigger({ name: "create.resume", schema: z.object({ firstName: z.string(), lastName: z.string(), photo: z.string(), email: z.string().email(), companies: z.array(z.object({ companyName: z.string(), position: z.string(), workedYears: z.string(), technologies: z.string() })) }), }), run: async (payload, io, ctx) => { } }); ``` 這將為我們建立一個名為「create-resume」的新工作。 如您所見,我們先前從「route.tsx」發送的請求進行了架構驗證。這將為我們提供驗證和“自動完成”。 我們將在這裡執行三項工作 - 聊天GPT - PDF建立 - 電子郵件發送 讓我們從 ChatGPT 開始。 [建立 OpenAI 帳戶](https://platform.openai.com/) 並產生 API 金鑰。 ![ChatGPT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ashau6i2sxcpd0qcxuwq.png) 從下拉清單中按一下「檢視 API 金鑰」以建立 API 金鑰。 ![ApiKey](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4bzc6e7f7avemeuuaygr.png) 接下來,透過執行下面的程式碼片段來安裝 OpenAI 套件。 ``` npm install @trigger.dev/openai ``` 將您的 OpenAI API 金鑰新增至 `.env.local` 檔案中。 ``` OPENAI_API_KEY=<your_api_key> ``` 在根目錄中建立一個名為「utils」的新資料夾。 在該目錄中,建立一個名為「openai.ts」的新文件 新增以下程式碼: ``` import { OpenAI } from "openai"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, }); export async function generateResumeText(prompt: string) { const response = await openai.completions.create({ model: "text-davinci-003", prompt, max_tokens: 250, temperature: 0.7, top_p: 1, frequency_penalty: 1, presence_penalty: 1, }); return response.choices[0].text.trim(); } export const prompts = { profileSummary: (fullName: string, currentPosition: string, workingExperience: string, knownTechnologies: string) => `I am writing a resume, my details are \n name: ${fullName} \n role: ${currentPosition} (${workingExperience} years). \n I write in the technologies: ${knownTechnologies}. Can you write a 100 words description for the top of the resume(first person writing)?`, jobResponsibilities: (fullName: string, currentPosition: string, workingExperience: string, knownTechnologies: string) => `I am writing a resume, my details are \n name: ${fullName} \n role: ${currentPosition} (${workingExperience} years). \n I write in the technolegies: ${knownTechnologies}. Can you write 3 points for a resume on what I am good at?`, workHistory: (fullName: string, currentPosition: string, workingExperience: string, details: TCompany[]) => `I am writing a resume, my details are \n name: ${fullName} \n role: ${currentPosition} (${workingExperience} years). ${companyDetails(details)} \n Can you write me 50 words for each company seperated in numbers of my succession in the company (in first person)?`, }; ``` 這段程式碼基本上建立了使用 ChatGPT 的基礎設施以及 3 個函數:「profileSummary」、「workingExperience」和「workHistory」。我們將使用它們來建立各部分的內容。 返回我們的「create.resume.ts」並新增作業: ``` import { client } from "@/trigger"; import { eventTrigger } from "@trigger.dev/sdk"; import { z } from "zod"; import { prompts } from "@/utils/openai"; import { TCompany, TUserDetails } from "@/components/Home"; const companyDetails = (companies: TCompany[]) => { let stringText = ""; for (let i = 1; i < companies.length; i++) { stringText += ` ${companies[i].companyName} as a ${companies[i].position} on technologies ${companies[i].technologies} for ${companies[i].workedYears} years.`; } return stringText; }; client.defineJob({ id: "create-resume", name: "Create Resume", version: "0.0.1", integrations: { resend }, trigger: eventTrigger({ name: "create.resume", schema: z.object({ firstName: z.string(), lastName: z.string(), photo: z.string(), email: z.string().email(), companies: z.array(z.object({ companyName: z.string(), position: z.string(), workedYears: z.string(), technologies: z.string() })) }), }), run: async (payload, io, ctx) => { const texts = await io.runTask("openai-task", async () => { return Promise.all([ await generateResumeText(prompts.profileSummary(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies[0].technologies)), await generateResumeText(prompts.jobResponsibilities(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies[0].technologies)), await generateResumeText(prompts.workHistory(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies)) ]); }); }, }); ``` 我們建立了一個名為「openai-task」的新任務。 在該任務中,我們使用 ChatGPT 同時執行三個提示,並返回它們。 --- ## 建立 PDF 建立 PDF 的方法有很多種 - 您可以使用 HTML2CANVAS 等工具並將 HTML 程式碼轉換為映像,然後轉換為 PDF。 - 您可以使用「puppeteer」之類的工具來抓取網頁並將其轉換為 PDF。 - 您可以使用不同的庫在後端建立 PDF。 在我們的例子中,我們將使用一個名為「jsPdf」的簡單函式庫,它是在後端建立 PDF 的非常簡單的函式庫。我鼓勵您使用 Puppeteer 和更多 HTML 來建立一些更強大的 PDF 檔案。 那我們來安裝它 ``` npm install jspdf @typs/jspdf --save ``` 讓我們回到「utils」並建立一個名為「resume.ts」的新檔案。該文件基本上會建立一個 PDF 文件,我們可以將其發送到使用者的電子郵件中。 加入以下內容: ``` import {TUserDetails} from "@/components/Home"; import {jsPDF} from "jspdf"; type ResumeProps = { userDetails: TUserDetails; picture: string; profileSummary: string; workHistory: string; jobResponsibilities: string; }; export function createResume({ userDetails, picture, workHistory, jobResponsibilities, profileSummary }: ResumeProps) { const doc = new jsPDF(); // Title block doc.setFontSize(24); doc.setFont('helvetica', 'bold'); doc.text(userDetails.firstName + ' ' + userDetails.lastName, 45, 27); doc.setLineWidth(0.5); doc.rect(20, 15, 170, 20); // x, y, width, height doc.addImage({ imageData: picture, x: 25, y: 17, width: 15, height: 15 }); // Reset font for the rest doc.setFontSize(12); doc.setFont('helvetica', 'normal'); // Personal Information block doc.setFontSize(14); doc.setFont('helvetica', 'bold'); doc.text('Summary', 20, 50); doc.setFontSize(10); doc.setFont('helvetica', 'normal'); const splitText = doc.splitTextToSize(profileSummary, 170); doc.text(splitText, 20, 60); const newY = splitText.length * 5; // Work history block doc.setFontSize(14); doc.setFont('helvetica', 'bold'); doc.text('Work History', 20, newY + 65); doc.setFontSize(10); doc.setFont('helvetica', 'normal'); const splitWork = doc.splitTextToSize(workHistory, 170); doc.text(splitWork, 20, newY + 75); const newNewY = splitWork.length * 5; // Job Responsibilities block doc.setFontSize(14); doc.setFont('helvetica', 'bold'); doc.text('Job Responsibilities', 20, newY + newNewY + 75); doc.setFontSize(10); doc.setFont('helvetica', 'normal'); const splitJob = doc.splitTextToSize(jobResponsibilities, 170); doc.text(splitJob, 20, newY + newNewY + 85); return doc.output("datauristring"); } ``` 該文件包含三個部分:「個人資訊」、「工作歷史」和「工作職責」區塊。 我們計算每個區塊的位置和內容。 一切都是以“絕對”的方式設置的。 值得注意的是“splitTextToSize”將文字分成多行,因此它不會超出螢幕。 ![恢復](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdolng9e5ojev895x8i5.png) 現在,讓我們建立下一個任務:再次開啟 `resume.ts` 並新增以下程式碼: ``` import { client } from "@/trigger"; import { eventTrigger } from "@trigger.dev/sdk"; import { z } from "zod"; import { prompts } from "@/utils/openai"; import { TCompany, TUserDetails } from "@/components/Home"; import { createResume } from "@/utils/resume"; const companyDetails = (companies: TCompany[]) => { let stringText = ""; for (let i = 1; i < companies.length; i++) { stringText += ` ${companies[i].companyName} as a ${companies[i].position} on technologies ${companies[i].technologies} for ${companies[i].workedYears} years.`; } return stringText; }; client.defineJob({ id: "create-resume", name: "Create Resume", version: "0.0.1", integrations: { resend }, trigger: eventTrigger({ name: "create.resume", schema: z.object({ firstName: z.string(), lastName: z.string(), photo: z.string(), email: z.string().email(), companies: z.array(z.object({ companyName: z.string(), position: z.string(), workedYears: z.string(), technologies: z.string() })) }), }), run: async (payload, io, ctx) => { const texts = await io.runTask("openai-task", async () => { return Promise.all([ await generateResumeText(prompts.profileSummary(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies[0].technologies)), await generateResumeText(prompts.jobResponsibilities(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies[0].technologies)), await generateResumeText(prompts.workHistory(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies)) ]); }); console.log('passed chatgpt'); const pdf = await io.runTask('convert-to-html', async () => { const resume = createResume({ userDetails: payload, picture: payload.photo, profileSummary: texts[0], jobResponsibilities: texts[1], workHistory: texts[2], }); return {final: resume.split(',')[1]} }); console.log('converted to pdf'); }, }); ``` 您可以看到我們新增了一個名為「convert-to-html」的新任務。這將為我們建立 PDF,將其轉換為 base64 並返回。 --- ## 讓他們知道🎤 我們即將到達終點! 剩下的唯一一件事就是與用戶分享。 您可以使用任何您想要的電子郵件服務。 我們將使用 Resend.com 造訪[註冊頁面](https://resend.com/signup),建立帳戶和 API 金鑰,並將其儲存到 `.env.local` 檔案中。 ``` RESEND_API_KEY=<place_your_API_key> ``` ![密鑰](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yncrarbwcs65j44fs91y.png) 將 [Trigger.dev Resend 整合套件](https://trigger.dev/docs/integrations/apis/resend) 安裝到您的 Next.js 專案。 ``` npm install @trigger.dev/resend ``` 剩下要做的就是加入我們的最後一項工作! 幸運的是,Trigger 直接與 Resend 集成,因此我們不需要建立新的「正常」任務。 這是最終的程式碼: ``` import { client } from "@/trigger"; import { eventTrigger } from "@trigger.dev/sdk"; import { z } from "zod"; import { prompt } from "@/utils/openai"; import { TCompany, TUserDetails } from "@/components/Home"; import { createResume } from "@/utils/resume"; import { Resend } from "@trigger.dev/resend"; const resend = new Resend({ id: "resend", apiKey: process.env.RESEND_API_KEY!, }); const companyDetails = (companies: TCompany[]) => { let stringText = ""; for (let i = 1; i < companies.length; i++) { stringText += ` ${companies[i].companyName} as a ${companies[i].position} on technologies ${companies[i].technologies} for ${companies[i].workedYears} years.`; } return stringText; }; client.defineJob({ id: "create-resume", name: "Create Resume", version: "0.0.1", integrations: { resend }, trigger: eventTrigger({ name: "create.resume", schema: z.object({ firstName: z.string(), lastName: z.string(), photo: z.string(), email: z.string().email(), companies: z.array(z.object({ companyName: z.string(), position: z.string(), workedYears: z.string(), technologies: z.string() })) }), }), run: async (payload, io, ctx) => { const texts = await io.runTask("openai-task", async () => { return Promise.all([ await generateResumeText(prompts.profileSummary(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies[0].technologies)), await generateResumeText(prompts.jobResponsibilities(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies[0].technologies)), await generateResumeText(prompts.workHistory(payload.firstName, payload.companies[0].position, payload.companies[0].workedYears, payload.companies)) ]); }); console.log('passed chatgpt'); const pdf = await io.runTask('convert-to-html', async () => { const resume = createResume({ userDetails: payload, picture: payload.photo, profileSummary: texts[0], jobResponsibilities: texts[1], workHistory: texts[2], }); return {final: resume.split(',')[1]} }); console.log('converted to pdf'); await io.resend.sendEmail('send-email', { to: payload.email, subject: 'Resume', html: 'Your resume is attached!', attachments: [ { filename: 'resume.pdf', content: Buffer.from(pdf.final, 'base64'), contentType: 'application/pdf', } ], from: "Nevo David <[email protected]>", }); console.log('Sent email'); }, }); ``` 我們在檔案頂部的「Resend」實例載入了儀表板中的 API 金鑰。 我們有 ``` integrations: { resend }, ``` 我們將其加入到我們的作業中,以便稍後在“io”內部使用。 最後,我們的工作是發送 PDF `io.resend.sendEmail` 值得注意的是其中的附件,其中包含我們在上一步中產生的 PDF 文件。 我們就完成了🎉 ![我們完成了](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/esfhlds2qv1013c6x2h3.png) 您可以在此處檢查並執行完整的源程式碼: https://github.com/triggerdotdev/blog --- ## 讓我們聯絡吧! 🔌 作為開源開發者,我們邀請您加入我們的[社群](https://discord.gg/nkqV9xBYWy),以做出貢獻並與維護者互動。請隨時造訪我們的 [GitHub 儲存庫](https://github.com/triggerdotdev/trigger.dev),貢獻並建立與 Trigger.dev 相關的問題。 本教學的源程式碼可在此處取得: https://github.com/triggerdotdev/blog/tree/main/blog-resume-builder 感謝您的閱讀! --- 原文出處:https://dev.to/triggerdotdev/creating-a-resume-builder-with-nextjs-triggerdev-and-gpt4-4gmf

React 設計模式 Design Patterns

![](https://refine.ams3.cdn.digitaloceanspaces.com/blog-banners/retool-alternative.png) ## 介紹: React 開發人員可以透過使用設計模式來節省時間和精力,設計模式提供了一種使用經過測試且可信賴的解決方案來解決問題的快速方法。它們支援低耦合的內聚模組,從而幫助 React 開發人員建立可維護、可擴展且高效的應用程式。在本文中,我們將探索 React 設計模式並研究它們如何改進 React 應用程式的開發。 ## 容器和表示模式 容器和表示模式是一種旨在將反應程式碼中的表示邏輯與業務邏輯分離的模式,從而使其模組化、可測試並遵循關注點分離原則。 大多數情況下,在 React 應用程式中,我們需要從後端/儲存取得資料或計算邏輯並在 React 元件上表示該計算的結果。在這些情況下,容器和表示模式大放異彩,因為它可用於將元件分為兩類,即: * 容器元件,充當負責資料取得或計算的元件。 * 表示元件,其工作是將獲取的資料或計算值呈現在 UI(使用者介面)上。 容器和表示模式的範例如下所示: ``` import React, { useEffect } from 'react'; import CharacterList from './CharacterList'; const StarWarsCharactersContainer:React.FC = () => { const [characters, setCharacters] = useState<Character>([]) const [isLoading, setIsLoading] = useState<boolean>(false); const [error, setError] = useState<boolean>(false); const getCharacters = async () => { setIsLoading(true); try { const response = await fetch("https://akabab.github.io/starwars-api/api/all.json"); const data = await response.json(); setIsLoading(false); if (!data) return; setCharacters(data); } catch(err) { setError(true); } finally { setIsLoading(true); } }; useEffect(() => { getCharacters(); }, []); return <CharacterList loading={loading} error={error} characters={characters} />; }; export default StarWarsCharactersContainer; ``` ``` // the component is responsible for displaying the characters import React from 'react'; import { Character } from './types'; interface CharacterListProps { loading: boolean; error: boolean; users: Character[]; } const CharacterList: React.FC<CharacterListProps> = ({ loading, error, characters }) => { if (loading && !error) return <div>Loading...</div>; if (!loading && error) return <div>error occured.unable to load characters</div>; if (!characters) return null; return ( <ul> {characters.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }; export default CharacterList; ``` ## 有 Hooks 的元件組合 Hooks 是 React 16.8 中首次推出的全新功能。從那時起,他們在開發 React 應用程式中發揮了至關重要的作用。掛鉤是基本函數,可授予功能元件存取狀態和生命週期方法(以前僅可用於類別元件)的功能。另一方面,掛鉤可以專門設計來滿足元件要求並具有其他用例。 我們現在可以隔離所有狀態邏輯(一種需要反應性狀態變數的邏輯),並使用自訂掛鉤在元件中組合或使用它。因此,程式碼更加模組化和可測試,因為鉤子鬆散地綁定到元件,因此可以單獨測試。 帶有鉤子的元件組合示例如下所示: ``` // creating a custom hook that fetches star wars characters export const useFetchStarWarsCharacters = () => { const [characters, setCharacters] = useState<Character>([]) const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(false); const controller = new AbortController() const getCharacters = async () => { setIsLoading(true); try { const response = await fetch( "https://akabab.github.io/starwars-api/api/all.json", { method: "GET", credentials: "include", mode: "cors", headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' }, signal: controller.signal } ); const data = await response.json(); setIsLoading(false); if (!data) return; setCharacters(data); } catch(err) { setError(true); } finally { setIsLoading(true); } }; useEffect(() => { getCharacters(); return () => { controller.abort(); } }, []); return [ characters, isLoading, error ]; }; ``` 建立自訂鉤子後,我們將其導入到我們的 **StarWarsCharactersContainer** 元件中並使用它; ``` // importing the custom hook to a component and fetch the characters import React from 'react'; import { Character } from './types'; import { useFetchStarWarsCharacters } from './useFetchStarWarsCharacters'; const StarWarsCharactersContainer:React.FC = () => { const [ characters, isLoading, error ] = useFetchStarWarsCharacters(); return <CharacterList loading={loading} error={error} characters={characters} />; }; export default StarWarsCharactersContainer; ``` --- <橫幅隨機/> --- ## 使用Reducers進行狀態管理 大多數情況下,處理元件中的許多狀態會導致許多未分組狀態的問題,這可能是處理起來很麻煩且具有挑戰性的。在這種情況下,減速器模式可能是有用的選擇。我們可以使用減速器將狀態分類為某些操作,這些操作在執行時可以變更分組的狀態。 此模式允許使用它的開發人員控制元件和/或掛鉤的狀態管理,讓他們在發送事件時管理狀態變更。 使用減速器模式的範例如下所示: ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbob3gmfxws8k4ti0cyx.png) 在上面的程式碼中,元件調度兩個操作: * “**login**”操作類型會觸發狀態更改,影響三個狀態值,即 **loggedIn**、**user**、**token**。 *“**註銷**”操作只是將狀態重設為其初始值。 ## 提供者的資料管理 提供者模式對於資料管理非常有用,因為它利用上下文 API 透過應用程式的元件樹傳遞資料。這種模式是一種有效的解決支柱鑽井問題的方法,這一直是 React 開發中普遍關注的問題。 為了實現提供者模式,我們首先建立一個提供者元件。 Provider 是 Context 物件提供給我們的一個高階元件。我們可以利用React提供的createContext方法來建構一個Context物件。 ``` export const ThemeContext = React.createContext(null); export function ThemeProvider({ children }) { const [theme, setTheme] = React.useState("light"); return ( <ThemeContext.Provider value={{ theme, setTheme }}> {children} </ThemeContext.Provider> ); } ``` 建立提供者後,我們將使用建立的提供者元件封裝依賴上下文 API 中的資料的元件。 為了從上下文 API 取得資料,我們呼叫 useContext 鉤子,它接受上下文作為參數(在本例中為 **ThemeContext**)。 ``` import { useContext } from 'react'; import { ThemeProvider, ThemeContext } from "../context"; const HeaderSection = () => { <ThemeProvider> <TopNav /> </ThemeProvider>; }; const TopNav = () => { const { theme, setTheme } = useContext(ThemeContext); return ( <div style={{ backgroundColor: theme === "light" ? "#fff" : "#000 " }}> ... </div> ); }; ``` ## 使用 HOC(高階元件)增強元件 高階元件接受一個元件作為參數,並傳回一個注入了附加資料或功能的增壓元件。 React 中 HOC 的可能性是由於 React 更喜歡組合而不是繼承。 高階元件 (HOC) 模式提供了一種增加或修改元件功能的機制,促進元件重複使用和程式碼共用。 HOC 模式的範例如下所示: ``` import React from 'react' const higherOrderComponent = Component => { return class HOC extends React.Component { state = { name: 'John Doe' } render() { return <Component name={this.state.name {...this.props} /> } } const AvatarComponent = (props) => { return ( <div class="flex items-center justify-between"> <div class="rounded-full bg-red p-4"> {props.name} </div> <div> <p>I am a {props.description}.</p> </div> </div> ) } const SampleHOC = higherOrderComponent(AvatarComponent); const App = () => { return ( <div> <SampleHOC description="Frontend Engineer" /> </div> ) } export default App; ``` 在上面的程式碼中, **<AvatarComponent/>** 由 **higherOrderComponent** 提供 props,它將在內部使用。 ## 複合元件 複合元件模式是一種 React 設計模式,用於管理由子元件組成的父元件。 這種模式背後的原理是將父元件分解為更小的元件,然後使用 props、上下文或其他反應資料管理技術來管理這些較小元件之間的互動。 當需要建立由較小元件組成的可重複使用、多功能元件時,這種模式會派上用場。它使開發人員能夠建立複雜的 UI 元件,這些元件可以輕鬆自訂和擴展,同時保持清晰簡單的程式碼結構。 複合元件模式的用例範例如下所示: ``` import React, { createContext, useState } from 'react'; const ToggleContext = createContext(); function Toggle({ children }) { const [on, setOn] = useState(false); const toggle = () => setOn(!on); return ( <ToggleContext.Provider value={{ on, toggle }}> {children} </ToggleContext.Provider> ); } Toggle.On = function ToggleOn({ children }) { const { on } = useContext(ToggleContext); return on ? children : null; } Toggle.Off = function ToggleOff({ children }) { const { on } = useContext(ToggleContext); return on ? null : children; } Toggle.Button = function ToggleButton(props) { const { on, toggle } = useContext(ToggleContext); return <button onClick={toggle} {...props} />; } function App() { return ( <Toggle> <Toggle.On>The button is on</Toggle.On> <Toggle.Off>The button is off</Toggle.Off> <Toggle.Button>Toggle</Toggle.Button> </Toggle> ); } ``` ## 道具組合 這需要從幾個相關的 props 建立一個物件,並將其作為單個 props 傳遞給元件。 這種模式允許我們清理程式碼並使管理 props 變得更簡單,當我們想要將大量相關屬性傳遞給元件時,它特別有用。 ``` import React from 'react'; function P(props) { const { color, size, children, ...rest } = props; return ( <p style={{ color, fontSize: size }} {...rest}> { children } </p> ); } function App() { const paragraphProps = { color: "red", size: "20px", lineHeight: "22px" }; return <P {...paragraphProps}>This is a P</P>; } ``` ## 受控輸入 受控輸入模式可用於處理輸入欄位。此模式涉及使用事件處理程序在輸入欄位的值發生變更時更新元件狀態,以及將輸入欄位的目前值儲存在元件狀態中。 由於React 控制元件的狀態和行為,因此該模式使程式碼比不受控制的輸入模式更具可預測性和可讀性,後者不使用元件的狀態,而是直接透過DOM(文件物件模型)對其進行控制。 受控輸入模式的用例範例如下所示: ``` import React, { useState } from 'react'; function ControlledInput() { const [inputValue, setInputValue] = useState(''); const handleChange = (event) => { setInputValue(event.target.value); }; return ( <input type="text" value={inputValue} onChange={handleChange} /> ); } ``` ## 使用forwardRefs 管理自訂元件 稱為 ForwardRef 的高階元件將另一個元件作為輸入並輸出一個傳遞原始元件引用的新元件。透過這樣做,子元件的 ref(可用於檢索底層 DOM 節點或元件實例)可供父元件存取。 當建立與第三方程式庫或應用程式中的另一個自訂元件互動的自訂元件時,在工作流程中包含 ForwardRef 模式非常有幫助。透過授予對庫的 DOM 節點或另一個元件的 DOM 實例的存取權限,它有助於將此類元件的控制權轉移給您。 forwardRef 模式的用例範例如下所示: ``` import React from "react"; const CustomInput = React.forwardRef((props, ref) => ( <input type="text" {...props} ref={ref} /> )); const ParentComponent = () => { const inputRef = useRef(null); useEffect(() => { inputRef.current.focus(); }, []); return <CustomInput ref={inputRef} />; }; ``` 在上面的程式碼中,我們使用「forwardRefs」從元件「<ParentComponent/>」觸發了另一個元件「<CustomInput/>」的焦點。 # 結論 我們在本文中討論了 React 設計模式,包括高階元件、容器呈現元件模式、複合元件、受控元件等等。透過將這些設計模式和最佳實踐合併到您的 React 專案中,您可以提高程式碼質量,促進團隊協作,並使您的應用程式更具可擴展性、靈活性和可維護性。 --- 原文出處:https://dev.to/refine/react-design-patterns-230o

🧠 自己用 JavaScript 手寫:人工智慧/神經網路......! 😱 不使用任何套件!🤯

您是否嘗試過實際建立神經網路?不,我也沒有……直到今天! 在這篇文章中,我們將介紹我學到的一些東西,以及一些用 vanilla JS 編寫的非常簡單的神經網路的 2 個演示。 ## 介紹 今天早些時候,我正在閱讀 @supabase_io [“AI 內容風暴”](https://supabase.com/blog/supabase-ai-content-storm) 文章。 我突然意識到一件事。我得到了神經網路......但我實際上根本沒有得到它們! 就像,我得到了神經元的概念。但數學是如何運作的呢? 特別是如何使用「反向傳播」來訓練神經網路?偏差和權重如何發揮作用?什麼或誰是 sigmoid? ETC。 現在,明智的做法是閱讀大量文章,獲取一個庫並使用它。 **但我不懂事。** 因此,我閱讀了大量文章……然後決定建立我的第一個神經網路。 但這還不夠難,所以我決定用 JavaScript 來做(因為每個人似乎都使用 Python...)。哦,我決定在沒有任何庫的情況下完成它。哦,我也想在其中建立一個視覺化工具。 我有些不對勁……我似乎在痛苦中茁壯成長。 無論如何,我做到了,這就是我學到的東西。 ## 注意:這不是教學課程 聽著,我想澄清一下,這不是教學! 這只是我分享一些我在學習和我的**第一個**神經網路時發現有趣的事情。 請注意,這裡強調**第一**,所以請不要將其視為除了有趣的東西之外的任何東西。 我也盡力解釋每個部分及其作用,但與所有事情一樣,你對某些東西越熟練,你就越能更好地解釋它......所以我的一些解釋可能有點“偏離” ! 無論如何,既然所有這些都已經解決了,讓我們繼續吧! 如果您想直接跳到[最終演示](#the-demo),請繼續! ## 第一步 好的,首先,我可以建立的最基本的神經網路是什麼? 經過一番閱讀後,我發現神經網路可以像一些輸入神經元和一些輸出神經元一樣簡單。 每個輸入神經元都連接到一個輸出神經元,然後我們可以為每個連接加入權重。 考慮到這一點,我必須想出一個易於理解的問題來解決,但又足夠複雜以確保我的網路正常運作。 我決定建立一個神經網絡,它獲取圖表上某個點的 X 和 Y 座標,然後根據它們是正還是負為它們分配一個「團隊」(顏色)。 這樣我們就有了 2 個輸入(X 和 Y 位置),然後有 4 個輸出: 1. X > 0 且 Y > 0 2. X < 0 且 Y > 0 3. X > 0 且 Y < 0 4. X < 0 且 Y < 0 由於這裡的要求非常簡單,我們可以擺脫一些「隱藏」神經元(這是我稍後將介紹的內容)並讓事情變得超級簡單! 所以本質上我們必須建構一個看起來像這樣的神經網路: ![左側 2 個圓圈透過線連接到右側 4 個圓圈。深色背景上的霓虹燈風格。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9rh1tbxrndmjsz5b7oo.png) 左邊的圓圈是我們的輸入(X 和 Y 位置),右邊的圓圈是我們之前討論的輸出。 ### 我們的第一個神經元 好的,現在我們可以真正開始了。 現在我實際上並沒有先建置神經元。事實上,我實際上首先建立了一個視覺化工具,因為這是查看事情是否正常運作的最簡單方法,但我稍後會介紹這一點。 因此,讓我們建立一個神經元(或更具體地說,一些神經元及其連接)。 幸運的是,神經元其實非常簡單! (或者我應該說,它們*可以*非常簡單......它們在大型語言模型(LLM)等中變得更加複雜。) 簡單神經元具有偏差(將其視為內部權重,我們將加入到最終計算中以對每個神經元進行加權的數字),並透過每個連接之間的權重連接到其他神經元。 現在回想起來,單獨加入到每個神經元的連接可能是一個更好的主意,但我決定將每層神經元和每層連接作為單獨的專案加入,因為這樣更容易理解。 所以建構我的第一個神經網路的程式碼如下所示: ``` class NeuralNetwork { constructor(inputLen, outputLen) { this.inputLen = inputLen; this.outputLen = outputLen; this.weights = Array.from({ length: this.outputLen }, () => Array.from({ length: this.inputLen }, () => Math.random()) ); this.bias = Array(this.outputLen).fill(0); } } const neuralNetwork = new NeuralNetwork(2, 4); ``` 好的,我跳過了一些步驟,所以讓我們簡要介紹一下每個部分。 `this.inputLen = inputLen;` 和 `this.outputLen = outputLen;` 只是為了讓我們可以引用輸入和輸出的數量。 `this.weights = [...]` 是連線。現在看起來可能有點嚇人,但這就是我們正在做的事情: - 建立輸出神經元陣列(`outputLen`) - 將長度為“inputLen”的陣列加入到每個陣列條目,並用 0 到 1 之間的一些隨機值填充它以開始我們的工作。 該程式碼的輸出範例如下所示: ``` this.weights = [ [0.7583747881712366,0.4306037998314902], [0.40553698492617807,0.4419651593960727], [0.852978801662627,0.9762509253699836], [0.8701610553353811,0.5583309725764114] ] ``` 它們本質上代表以下內容: ``` [input 1 to output 1, input 2 to output 1], [input 1 to output 2, input 2 to output 2], [input 1 to output 3, input 2 to output 3], [input 1 to output 4, input 2 to output 4], ``` 然後我們還有「this.bias」。 這適用於輸出層中的每個神經元。我們稍後用它來加入輸出值,使一些神經元更強,一些神經元更弱。 它只是一個由 4 個 0 組成的陣列來讓我們開始,因為我們不想要初始偏差! 現在,雖然這是一個神經網絡,但它完全沒有用。 我們無法實際使用它......如果我們確實使用它,它產生的結果將是完全隨機的! 所以我們需要解決這些問題! ### 使用我們的網路! 我們需要做的第一件事是實際獲取一些輸入,透過我們的網路執行它們並收集輸出。 這是我想出來的: ``` propagate(inputs) { const output = new Array(this.outputLen); for (let i = 0; i < this.outputLen; i++) { output[i] = 0; for (let j = 0; j < this.inputLen; j++) { output[i] += this.weights[i][j] * inputs[j]; } output[i] += this.bias[i]; output[i] = this.sigmoid(output[i]); } return output; } sigmoid(x) { return 1 / (1 + Math.exp(-x)); } ``` 現在這裡有兩件有趣的事情。 ####乙狀結腸 首先,一件有趣的事情是我們的“sigmoid”函數。它所做的只是沿著「S 形」曲線將我們輸入的值(例如 12)轉換為 0 到 1 之間的值。 這是我們將價值觀從極端標準化為更統一的**且始終為正值的方式。** 進一步閱讀後,這裡還有其他關於如何將值更改為 0 到 1 之間的選項,但我還沒有完全探索它們(例如 [ReLU](https://en.wikipedia.org/wiki/Rectifier_(neural_networks)) 如果你想閱讀相關內容)。 我確信有一些很好的解釋為什麼需要這樣做,但在我的猴腦中,這只是將值保持在 0 和 1 之間的方法,以便乘法保持在一定範圍內並且值被「展平」。 這樣你就不會在過於強大的神經元之間出現「失控」的路徑。 例如,假設您有一個權重為 16 的連接和一個權重為 1 的連接,使用我們的 sigmoid 函數,我們可以將其從 16 倍的差異減少到大約 35% 的差異(「sigmoid(1)」為0.73 ,執行我們的函數後,`sigmoid(16)` 為0.99)。 這也意味著負值變成正值。 因此,透過 sigmoid 函數執行值意味著負數會轉換為 0 到 0.5 之間的值,0 的值恰好變成 0.5,大於 0 的值變成 0.5 到 1 之間的值。 ![描繪兩條虛線之間的 S 型曲線的圖形。中間有一條虛線表示0.5。深色背景上的霓虹燈發光造型。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dr3460emdf5m77nc0rn2.png) 如果你考慮一下,這是有道理的,因為當我們開始將負數和正數相乘時,我們可以極大地改變我們的輸出。 例如,如果我們在 100 的路徑中有一個負神經元,而其餘的都是正神經元,這會將強值更改為弱值,並且可能會導致問題。 不管怎樣,隨著我閱讀更多和實驗更多,我相信我會更好地理解這一部分! #### 我需要偏見嗎? 第二個有趣的是「output[i] += this.bias[i];」。 好吧,在這個神經網路中,所有 4 個輸出都同樣重要,而且我們沒有隱藏神經元,所以我後來刪除了它以簡化程式碼! 但諷刺的是,在我們更複雜的神經網路上,由於網路反向傳播的工作方式,我需要重新加入輸出神經元的偏差。否則,一個輸出神經元會一直啟動。 我無法弄清楚這是否是必要的步驟,或者我的神經網路是否犯了一個錯誤,而這是為了彌補它。 再次提醒大家,我還在學習中,只是掌握了基礎知識,所以我不知道它是什麼! 🤣 #### 我們快到了 上面的其餘程式碼相當簡單。我們只是將每個輸入乘以與每個輸出相關的權重(並加入不必要的偏差!)。 事實上,我們現在就可以執行它,但結果會很糟糕!讓我們來解決這個問題! ### 訓練時間到了! 好吧,神經網路的最後一個重要部分是訓練它! 現在,隨著這篇文章越來越長,我將只介紹以下訓練程式碼的要點(順便說一下,我花了將近一個小時來編寫......我告訴過你我對此是個菜鳥! ) ``` train(inputs, target) { const output = this.propagate(inputs); const errors = new Array(this.outputLen); for (let i = 0; i < this.outputLen; i++) { errors[i] = target[i] - output[i]; for (let j = 0; j < this.inputLen; j++) { this.weights[i][j] += this.learningRate * errors[i] * output[i] * (1 - output[i]) * inputs[j]; } this.bias[i] += this.learningRate * errors[i]; } } ``` “為什麼花了這麼長時間?”我聽到你問了!好吧,它讓我思考了所有需要相乘才能更新每個權重的位元。 另外 `this.learningRate` 也需要一點時間來適應。這只是降低我們改變權重的速率,這樣我們就不會“超過”每個權重的目標值,但是將其調整到合理的值需要經驗......我沒有經驗並且設置得太低,所以我的程式碼看起來被破壞了! 經過一番擺弄後,我將數值定為 0.1(而不是 0.01 🤦🏼‍♂️),突然間事情開始變得更好了! 是的,所以我們有一個訓練函數。但請記住,此訓練功能僅進行一次訓練。 我們需要對我們的網路進行多次訓練,希望每次訓練都能使其更加準確。 我們將在一秒鐘內討論這一點,但我想分享一個快速的側面/我學到的東西。 #### 訓練資料調整 我知道我們甚至還沒有涵蓋最終的訓練資料,但這是我學到的一個有趣的觀點,適合這裡(因為它解釋了為什麼我花了這麼長時間來編寫這個訓練函數)。 最初我產生了數百個不同的訓練 X 和 Y 座標,全部都是隨機的。 但經過進一步閱讀後,我只產生 4 個靜態訓練點,得到了更好的結果: ``` const trainingData = [ { x: -0.5, y: -0.5, label: "blue" }, { x: 0.5, y: -0.5, label: "red" }, { x: -0.5, y: 0.5, label: "green" }, { x: 0.5, y: 0.5, label: "purple" } ]; ``` 一旦你明白了,就很有意義了! 我們希望「拉」值更接近目標,上述值是我們每個區域的確切「中心點」。 因此,對於給定的距離,我們的錯誤率將始終保持一致。 這意味著我們的神經網路學習得更快,因為我們的錯誤率更大,這取決於它們距離 X 更遠還是距離 Y 更遠。 我可以更好地解釋這一點,但這超出了本文的範圍。希望如果您仔細考慮一下,那麼它也會像對我一樣為您「點擊」! 諷刺的是,我回到了更大模型的更隨機的資料集,因為我想真正測試我對學習率、過度訓練等的理解。 ## 我們有一個有效且有用的神經網路! 實際上,這就是我們的整個神經網路。 不過,我們需要做一件事。 我們的訓練函數需要執行很多次! 因此,我們需要最後一個函數來做到這一點,它獲取我們的訓練資料並執行我們的訓練函數數百次: ``` function train() { for (let i = 0; i < 10000; i++) { const data = trainingData[Math.floor(Math.random() * trainingData.length)]; neuralNetwork.train([data.x, data.y], encode(data.label)); } console.log("Training complete"); } ``` ### 金髮女孩迭代 請注意,我們在「for」循環中訓練網路 10,000 次。 10,000 次迭代足以訓練這個特定的神經網路。但對於我們稍後將介紹的更複雜的問題,我需要更多的迭代(並降低學習率)。 這是機器學習有趣的部分之一,您需要足夠地訓練神經網路(這很難做到正確),但如果訓練太多,就會發生“過度擬合”,並且實際上開始得到更糟糕的結果。因此,為了獲得最佳結果,需要完美平衡! 不管怎樣,已經很多了,我們終於迎來了第一個示範! ## 簡單的普通 JS 神經網路演示 雖然有點亂,但我們的神經網路和所有訓練部分都在下面 CodePen 的前 67 行。 其餘程式碼行實際上執行我們的網路(“neuralNetwork.propagate([x, y]);”大約第 85 行),然後將點及其預測顏色輸出到“<canvas>”上。 「encode」和「decode」純粹是為了獲取我們的輸出神經元,找到哪個神經元具有最高的激活,然後將其映射到我們可視化的顏色。 這是最後要理解的事。我們的輸出神經元都會有一個值。神經網路不僅僅輸出 1, 0, 0, 0。 相反,它會輸出每個輸出神經元的「確定性」或猜測。因此我們將得到類似「0.92,0.76,0.55,0.87」的輸出。 這就是為什麼我們有“解碼”函數,它找到最高輸出的神經元並將其作為我們的最終猜測! ``` // this line finds the max value of all of our output neurons and then returns its index so we can use that to classify our X and Y coordinates. const maxIndex = output.indexOf(Math.max(...output)); ``` ### 用法和實際演示 要使用該範例,您有 3 個按鈕: - **訓練** - 在我們的神經網路開始未經訓練和隨機化時對其進行訓練。 - **分類點** - 這是為了執行我們的神經網路。它將在圖表上繪製點並為它們分配顏色。我建議在訓練之前和之後執行這個。 - **重設** - 這將建立一個未經訓練的新神經網路。非常適合在訓練前後測試點的分類。 另請注意,每個區域都根據該區域應顯示的顏色進行著色。它真的可以讓您看到隨機且未經訓練的神經網路距離成功還有多遠(重置然後對測試點進行分類)! 玩吧! https://codepen.io/GrahamTheDev/pen/abPxoqb ### 我們最基本的神經網路結束 這樣我們就有了最基本的神經網路! 它可以很好地滿足我們的需求,並且我們設法了解了一些關於反向傳播(我們在主類別中的“train”函數)以及權重和偏差的知識。 但這是非常有限的。如果我們將來想做更高級的事情,我們需要加入一些隱藏神經元! ## 版本 2 - 隱藏神經元 好的,那為什麼要隱藏神經元呢?他們的目的是什麼? 在更複雜的範例中,它們充當獲取輸入並為它們的分類方式加入更多維度的方式。 我們仍然使用 2 個輸入神經元和 4 個輸出神經元,但這次我們在中間加入了一個附加層(我們可以更改和調整其中神經元的數量)。 所以我們的神經網路看起來像這樣: ![左側 2 個圓圈透過線連接到中間的 6 個圓圈,然後透過線連接到右側的 4 個圓圈。深色背景上的霓虹燈風格。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0f1204y2d96wucq6fzv.png) 由於神經網路需要處理更多的輸入並進行更複雜的計算,隱藏層中的額外神經元使它們能夠更好地對輸入進行分類並提供更好的結果。 隱藏層也可以是不同的「深度」。 假設我們有 2 個輸入神經元。我們可以將它們連接到 6 個「隱藏」神經元,然後將它們連接到 4 個輸出神經元。 但我們也可以將第一層的 6 個神經元連結到第二層隱藏神經元。第二層可能有 8 個神經元,然後連接到我們的 4 個輸出神經元。 但要遵循的內容還有很多,這是為了讓我學習基礎知識,所以我選擇加入一個隱藏層。這也意味著我可以將每個連接層保留為單獨的陣列,這在現階段更容易理解! ### 那麼有什麼新內容呢? 沒有太大變化,只是我們有了更多的連結和更多的神經元! 您可以將其視為串聯加入 2 個原始神經網絡,只是第一個神經網路的輸出現在充當第二個神經網路的輸入。 雖然程式碼可能更加複雜,但我們的神經網路遵循相同的原則。 這是程式碼: ``` class NeuralNetwork { constructor(inputSize, hiddenSize, outputSize) { this.inputSize = inputSize; this.hiddenSize = hiddenSize; this.outputSize = outputSize; this.weightsInputToHidden = Array.from({ length: hiddenSize }, () => Array.from({ length: inputSize }, () => Math.random() * 2 - 1) ); this.biasHidden = Array(hiddenSize).fill(0); this.weightsHiddenToOutput = Array.from({ length: outputSize }, () => Array.from({ length: hiddenSize }, () => Math.random() * 2 - 1) ); this.biasOutput = Array(outputSize).fill(0); this.learningRate = document.querySelector('#learningRate').value; // Adjusted learning rate this.hiddenLayer = new Array(this.hiddenSize); } feedForward(inputs) { for (let i = 0; i < this.hiddenSize; i++) { this.hiddenLayer[i] = 0; for (let j = 0; j < this.inputSize; j++) { this.hiddenLayer[i] += this.weightsInputToHidden[i][j] * inputs[j]; } this.hiddenLayer[i] += this.biasHidden[i]; this.hiddenLayer[i] = sigmoid(this.hiddenLayer[i]); } const output = new Array(this.outputSize); for (let i = 0; i < this.outputSize; i++) { output[i] = 0; for (let j = 0; j < this.hiddenSize; j++) { output[i] += this.weightsHiddenToOutput[i][j] * this.hiddenLayer[j]; } output[i] += this.biasOutput[i]; output[i] = sigmoid(output[i]); } return output; } train(inputs, target) { for (let i = 0; i < this.hiddenSize; i++) { this.hiddenLayer[i] = 0; for (let j = 0; j < this.inputSize; j++) { this.hiddenLayer[i] += this.weightsInputToHidden[i][j] * inputs[j]; } this.hiddenLayer[i] += this.biasHidden[i]; this.hiddenLayer[i] = sigmoid(this.hiddenLayer[i]); } const output = new Array(this.outputSize); for (let i = 0; i < this.outputSize; i++) { output[i] = 0; for (let j = 0; j < this.hiddenSize; j++) { output[i] += this.weightsHiddenToOutput[i][j] * this.hiddenLayer[j]; } output[i] += this.biasOutput[i]; output[i] = sigmoid(output[i]); } const errorsOutput = new Array(this.outputSize); const errorsHidden = new Array(this.hiddenSize); for (let i = 0; i < this.outputSize; i++) { errorsOutput[i] = target[i] - output[i]; for (let j = 0; j < this.hiddenSize; j++) { this.weightsHiddenToOutput[i][j] += this.learningRate * errorsOutput[i] * output[i] * (1 - output[i]) * this.hiddenLayer[j]; } this.biasOutput[i] += this.learningRate * errorsOutput[i]; } for (let i = 0; i < this.hiddenSize; i++) { errorsHidden[i] = 0; for (let j = 0; j < this.outputSize; j++) { errorsHidden[i] += this.weightsHiddenToOutput[j][i] * errorsOutput[j]; } this.biasHidden[i] += this.learningRate * errorsHidden[i]; for (let j = 0; j < this.inputSize; j++) { this.weightsInputToHidden[i][j] += this.learningRate * errorsHidden[i] * this.hiddenLayer[i] * (1 - this.hiddenLayer[i]) * inputs[j]; } } } } ``` 現在,不要被嚇倒,我剛剛複製了幾個循環,其中要操作的目標資料集略有不同。 我們加入了一組額外的偏差(對於我們的隱藏層)和一組額外的連接:我們的輸入層到我們的隱藏層,然後我們的隱藏層現在連接到我們的輸出層。 最後,我們的「train」函數有一些額外的循環,只是為了透過每個步驟進行反向傳播。 唯一值得一提的其他變化是我們現在有第三個輸入參數(中間),用於隱藏神經元的數量。 ### 醜陋,但似乎有用 看,我想再說一遍,這是我一邊學習一邊學習,所以程式碼反映了這一點。 這裡有很多重複,可擴展性不太好。 然而,據我所知,它是有效的。 話雖如此,雖然它有效,但它的性能似乎比我們原來的、簡單得多的神經網路要差。 這要么意味著我犯了一個錯誤(可能),要么是我沒有「撥入」正確的訓練設定。 說到這裡... ### 加入一些變數來玩 由於這更複雜,我在一些快速設定中「迴避」了。 現在我們可以更新: - **訓練資料大小** - 我們產生的不同隨機點的數量 - **訓練迭代** - 我們從訓練集中選擇隨機資料點並將其輸入神經網路上的「訓練」函數的次數。 - **學習率** - 我們根據錯誤調整速度的乘數。 - **隱藏節點(超過2個!)** - 調整第二層有多少個隱藏節點(需要您再次初始化網絡,否則會損壞!) - **要分類的點** - 傳遞給我們訓練過的神經網路並繪製在圖表上的點數。 這意味著我們可以更快地處理這些值,看看它們對我們的神經網路及其準確性有什麼影響! ### 最後一件事 哦,我加入了一個按鈕來視覺化神經網路的樣子。 無論如何按“可視化神經元和權重”,但它還沒有完成。我也沒有立即完成它的打算,因為我想完全重新設計建構神經網路的方法,使其更具可擴展性。 不過,按鈕就在那裡,請隨意按下。更好的是,請隨時為我修復它! 🤣💗 ## 演示 控制項與以前相同,加上前面 2 個小節中提到的輸入。 試試一下,看看是否可以微調學習率、神經元數量和訓練設定以獲得真正準確的結果! https://codepen.io/GrahamTheDev/pen/qBLwBxP 請務必更新一些值,重新初始化神經網絡,嘗試使用不同數量的隱藏神經元等。 如果您像我一樣是初學者,希望您能開始理解一些事情! ## 結論 用 vanilla JS 建立神經網路真的很有趣。 我沒有見過很多人這樣做,所以我希望它對你或至少對某人有用! 我學到了很多關於偏差、反向傳播(神經網路的關鍵)等的知識。 顯然這個例子和這裡學到的東西只是機器學習的1%。但對於像我這樣的微小的、未優化的神經網路和巨大的數十億參數模型來說,核心原理是相同的。 這個例子就像機器學習 (ML) 和神經網路的「hello world」。 接下來,我真的想嘗試建立一個更大的、結構更好、更容易擴展的神經網絡,看看我們是否可以進行一些光學字元辨識(OCR)。您可以將其視為機器學習和神經網路的“待辦事項清單”! ### 發表評論。 您是神經網路專家嗎?告訴我我哪裡錯了! 你是像我一樣的初學者嗎?那麼請告訴我這是否有助於您理解,至少一點點!或者,如果這實際上讓事情變得更加混亂! 😱 最重要的是,如果這篇文章激發了您對我糟糕的編碼做鬼臉,或者想要建置您自己的神經網絡......那麼我很高興它對您產生了一些影響,並且很樂意聽到它! 💗 --- 原文出處:https://dev.to/grahamthedev/a-noob-learns-ai-my-first-neural-networkin-vanilla-jswith-no-libraries-1f92

使用 CSS 建立漂亮的 HTML 表格

原文出處:https://dev.to/dcodeyt/creating-beautiful-html-tables-with-css-428l 讓您的 HTML 表格看起來很棒很容易 - 在今天的帖子中,我們將看看大約 30 行 CSS 來做到這一點! https://www.youtube.com/watch?v=biI9OFH6Nmg ### 寫 HTML 讓我們為該表編寫一些樣板 HTML 程式碼。 ``` <table class="styled-table"> <thead> <tr> <th>Name</th> <th>Points</th> </tr> </thead> <tbody> <tr> <td>Dom</td> <td>6000</td> </tr> <tr class="active-row"> <td>Melissa</td> <td>5150</td> </tr> <!-- and so on... --> </tbody> </table> ``` 請注意,我們有兩個類別: * `.styled-table` 這樣我們就不會竄改網站上的每個 `<table>` * `.active-row` 將是「活動」行 - 這個類別用於突出顯示特定行並將獲得它自己的 CSS,我們很快就會看到 ### 表格樣式 讓我們先定位主要的「<table>」元素: ``` .styled-table { border-collapse: collapse; margin: 25px 0; font-size: 0.9em; font-family: sans-serif; min-width: 400px; box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); } ``` 其中大部分都是不言自明的,但讓我們來看看主要的: * `box-shadow` 在桌子周圍加入微妙的透明陰影 * `border-collapse` 確保單元格邊框之間沒有空格 ### 設定標題樣式 對於標題,我們可以簡單地更改背景顏色並對文字應用一些基本樣式: ``` .styled-table thead tr { background-color: #009879; color: #ffffff; text-align: left; } ``` ### 移至表格儲存格 讓我們把事情分開一點: ``` .styled-table th, .styled-table td { padding: 12px 15px; } ``` ### 以及表格行... 為此,我們要做三件事: 1. 為每行新增下邊框進行分隔 2. 每隔一行新增淺色背景以提高可讀性 3. 在最後一行加上深色邊框以表示表格的結尾 ``` .styled-table tbody tr { border-bottom: 1px solid #dddddd; } .styled-table tbody tr:nth-of-type(even) { background-color: #f3f3f3; } .styled-table tbody tr:last-of-type { border-bottom: 2px solid #009879; } ``` ### 最後,讓活動行看起來有所不同 為此,我們只需對文字進行更改: ``` .styled-table tbody tr.active-row { font-weight: bold; color: #009879; } ``` **就是這樣!** 如果您有任何改進建議,請在下面的回覆中告訴我 😁 ## [立即報名👉 JavaScript DOM 速成課程](https://www.udemy.com/course/the-ultimate-javascript-dom-crash-course/?referralCode=DC343E5C8ED163F337E1) 如果您正在學習 Web 開發,您可以在下面的連結中找到有關 JavaScript DOM 的完整課程👇 [https://www.udemy.com/course/the-ultimate-javascript-dom-crash-course/?referralCode=DC343E5C8ED163F337E1](https://www.udemy.com/course/the-ultimate-javascript-dom-crash-course/?referralCode=DC343E5C8ED163F337E1) ![課程縮圖](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d2z3timz15pb93howzzk.jpg)

在 Ubuntu 18.04 LTS 上安裝 Z-shell (Oh My Zsh)

![Ubuntu 的 Z-shell](https://thepracticaldev.s3.amazonaws.com/i/qmdocrps1ngl6v1ox29n.png) - 更新軟體包 ``` sudo apt-get update sudo apt upgrade ``` - 安裝必備包(ZSH、powerline 和 powerline 字型) ``` sudo apt install zsh sudo apt-get install powerline fonts-powerline ``` - 克隆 Oh My Zsh Respo ``` git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh ``` - 建立一個新的ZSH設定檔 ``` cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc ``` - 為您的終端設定一個精美的主題 - 使用 Nano 編輯器開啟 `.zshrc` 文件 ``` nano .zshrc ``` - 找到行“ZSH_THEME =“robbyrussell””,將“robbyrussell”替換為“.zshrc”檔案中的“agnoster”主題(CTRL + X並輸入儲存) ``` ZSH_THEME="agnoster" ``` - 更改您的預設外殼 ``` chsh -s /bin/zsh ``` - 更新和卸載 oh-my-zsh 存取 - https://github.com/robbyrussell/oh-my-zsh#manual-updates ``` cd .oh-my-zsh upgrade_oh_my_zsh ``` ### 想要語法突出顯示嗎?為 Oh My Zsh 安裝 ZSH 語法突出顯示 - 克隆 ZSH 語法加亮顯示 ``` git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "$HOME/.zsh-syntax-highlighting" --depth 1 ``` - 在`.zshrc`配置中加入語法高亮 ``` echo "source $HOME/.zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" >> "$HOME/.zshrc" ``` #### 恢復到預設外殼 ``` chsh -s /bin/bash ``` --- 原文出處:https://dev.to/mskian/install-z-shell-oh-my-zsh-on-ubuntu-1804-lts-4cm4

最漂亮的 50 個 VS Code 主題

原文出處:https://dev.to/softwaredotcom/50-vs-code-themes-for-2020-45cc 如果您正在尋找新的主題來在新的一年裡改變您的程式碼編輯器,我隨時為您提供協助!看看具有獨特調色板的各種時尚主題 - 從時尚到時髦到充滿活力以及介於兩者之間的一切 - 看看什麼最適合您。我甚至加入了一些有趣的圖標包來進一步自訂 VS Code。 我將這些 VS Code 主題分為以下部分: * [熱門](#trending) (1-20) * [深色](#dark) (21-30) * [光](#光) (31-40) * [多彩](#colorful) (41-50) * [獎勵:圖示](#icons) (51-56) 要在 VS Code 中安裝主題,只需存取市場並選擇您想要下載的主題。若要在已安裝的主題之間切換,請使用「CMD/CTRL + SHIFT + P」開啟命令調色板,然後輸入「首選項:顏色主題」。然後您可以在菜單中瀏覽您的主題。 ## 熱門 發現越來越流行的 VS Code 新趨勢主題。 ### 1. [激進](https://marketplace.visualstudio.com/items?itemName=dhedgecock.radical-vscode) ![](https://raw.githubusercontent.com/DHedgecock/radical-vscode/master/assets/editor.jpg) ### 2.[礦箱材質](https://marketplace.visualstudio.com/items?itemName=sainnhe.mine.box-material) ![](https://user-images.githubusercontent.com/37491630/69468770-671d3400-0d85-11ea-85a7-cf7ffedab468.png) ### 3. [Merko](https://marketplace.visualstudio.com/items?itemName=merko.merko-green-theme) ![](https://github.com/merko30/merko-green-theme/raw/master/img/s.png) ### 4. [東京之夜](https://marketplace.visualstudio.com/items?itemName=enkia.tokyo-night) ![](https://raw.githubusercontent.com/enkia/tokyo-night-vscode-theme/master/static/ss_tokyo_night.png) ### 5. [補救措施](https://marketplace.visualstudio.com/items?itemName=robertrossmann.remedy) ![](https://raw.githubusercontent.com/robertrossmann/vscode-remedy/master/resources/screenshots/gui.png) ### 6. [最小](https://marketplace.visualstudio.com/items?itemName=dawranliou.minimal-theme-vscode) ![](https://github.com/dawran6/minimal-theme-vscode/raw/master/screenshot.png) ### 7. [Aurora X](https://marketplace.visualstudio.com/items?itemName=marqu3s.aurora-x) ![](https://github.com/marqu3s10/Aurora-X/raw/master/images/screenshot.png) ### 8. [大西洋之夜](https://marketplace.visualstudio.com/items?itemName=mrpbennett.atlantic-night) ![](https://github.com/mrpbennett/atlantic-night-vscode-theme/raw/master/imgs/first-screen.png) ### 9. [玻璃使用者介面](https://marketplace.visualstudio.com/items?itemName=aregghazaryan.glass-ui) ![](https://user-images.githubusercontent.com/38076644/62824174-54eb0180-bbab-11e9-975e-2baf0e8cf33f.png) ### 10. [淡淡的丁香](https://marketplace.visualstudio.com/items?itemName=alexnho.a-touch-of-lilac-theme) ![](https://raw.githubusercontent.com/alexnho/vscode-a-touch-of-lilac-theme/master/images/workbench.png) ### 11. [FireFly Pro](https://marketplace.visualstudio.com/items?itemName=ankitcode.firefly) ![](https://raw.githubusercontent.com/ankitmlive/firefly-theme/master/assets/second-demo.png) ### 12. [ReUI](https://marketplace.visualstudio.com/items?itemName=barrsan.reui) ![](https://raw.githubusercontent.com/barrsan/react-italic-theme-vscode/master/sc.png) ### 13. [史萊姆](https://marketplace.visualstudio.com/items?itemName=smlombardi.slime) ![](https://github.com/smlombardi/theme-slime/raw/master/screenshots/screenshot.png) ### 14. [Signed Dark Pro](https://marketplace.visualstudio.com/items?itemName=enenumxela.signed-dark-pro) ![](https://raw.githubusercontent.com/alex-munene/vscode-signed-dark-pro/master/images/signed-dark-pro.png) ### 15. [黑暗](https://marketplace.visualstudio.com/items?itemName=wart.ariake-dark) ![](https://github.com/a-wart/ariake-dark/blob/master/misc/screenshot_frag.png?raw=true) ### 16. [時髦燈](https://marketplace.visualstudio.com/items?itemName=loilo.snazzy-light) ![](https://raw.githubusercontent.com/loilo/vscode-snazzy-light/master/screenshots/javascript.png) ### 17. [Spacegray](https://marketplace.visualstudio.com/items?itemName=ionutvmi.spacegray-vscode) ![](https://raw.githubusercontent.com/ionutvmi/spacegray-vscode/master/screenshots/eighties.png) ### 18. [Celestial](https://marketplace.visualstudio.com/items?itemName=apvarun.celestial) ![](https://github.com/apvarun/celestial-theme/raw/master/Preview.png) ### 19. [藍莓黑](https://marketplace.visualstudio.com/items?itemName=peymanslh.blueberry-dark-theme) ![](https://raw.githubusercontent.com/peymanslh/vscode-blueberry-dark-theme/master/screenshot.png) ### 20. [熊](https://marketplace.visualstudio.com/items?itemName=dahong.theme-bear) ![](https://raw.githubusercontent.com/shaodahong/theme-bear/master/bear-theme-snap.png) ## 深色 您喜歡在黑暗中工作嗎?發現 VS Code 的一些最佳深色主題。 您也可以透過安裝我們的[最佳深色主題包](https://marketplace.visualstudio.com/items?itemName=thegeoffstevens.best-dark-themes-pack)來安裝所有這些深色主題。 ### 21. [一暗專業版](https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme) ![](https://raw.githubusercontent.com/Binaryify/OneDark-Pro/master/static/screenshot2.png) ### 22. [德古拉官方](https://marketplace.visualstudio.com/items?itemName=dracula-theme.theme-dracula) ![](https://github.com/dracula/visual-studio-code/raw/master/screenshot.png) ### 23. [Nord](https://marketplace.visualstudio.com/items?itemName=arcticicestudio.nord-visual-studio-code) ![](https://raw.githubusercontent.com/arcticicestudio/nord-docs/develop/assets/images/ports/visual-studio-code/ui-overview-jsx.png) ### 24. [Palenight](https://marketplace.visualstudio.com/items?itemName=whizkydee.material-palenight-theme) ![](https://i.imgur.com/1mYWEP4.png) ### 25. [One Monokai](https://marketplace.visualstudio.com/items?itemName=azemoh.one-monokai) ![](https://github.com/azemoh/vscode-one-monokai/raw/master/screenshot-v0.2.0.png) ### 26. [夜貓子](https://marketplace.visualstudio.com/items?itemName=sdras.night-owl) ![](https://github.com/sdras/night-owl-vscode-theme/raw/master/first-screen.jpg) ### 27. [仙女座](https://marketplace.visualstudio.com/items?itemName=EliverLara.andromeda) ![](https://github.com/EliverLara/Andromeda/raw/master/images/andromeda.png) ### 28. [Darcula](https://marketplace.visualstudio.com/items?itemName=rokoroku.vscode-theme-darcula) ![](https://github.com/rokoroku/vscode-theme-darcula/raw/master/screenshot.png) ### 29. [地平線主題](https://marketplace.visualstudio.com/items?itemName=jolaleye.horizon-theme-vscode) ![](https://i.imgur.com/y0gi1ez.png) ### 30. [Cobalt2](https://marketplace.visualstudio.com/items?itemName=wesbos.theme-cobalt2) ![](https://raw.githubusercontent.com/wesbos/cobalt2-vscode/cobalt2-updates/images/ss.png) ## 光 想要程式碼編輯器更輕一些嗎?看看這些時尚的淺色主題。 您也可以透過安裝我們的[最佳淺色主題包](https://marketplace.visualstudio.com/items?itemName=thegeoffstevens.best-light-themes-pack)來安裝所有這些淺色主題。 ### 31. [原子一燈](https://marketplace.visualstudio.com/items?itemName=akamud.vscode-theme-onelight) ![](https://raw.githubusercontent.com/akamud/vscode-theme-onelight/master/screenshots/preview.png) ### 32. [Bluloco Light](https://marketplace.visualstudio.com/items?itemName=uloco.theme-bluloco-light) ![](https://raw.githubusercontent.com/uloco/theme-bluloco-light/master/screenshots/js.png) ### 33. [Brackets Light Pro](https://marketplace.visualstudio.com/items?itemName=fehey.brackets-light-pro) ![](https://raw.githubusercontent.com/EryouHao/brackets-light-pro/master/static/screenshot.png) ### 34. [作家](https://marketplace.visualstudio.com/items?itemName=xaver.theme-writer) ![](https://github.com/xaverh/theme-yscritwr/raw/master/screenshot.png) ### 35. [NetBeans Light](https://marketplace.visualstudio.com/items?itemName=obrejla.netbeans-light-theme) ![](https://github.com/obrejla/vscode-netbeans-light-theme/raw/master/images/vscode-netbeans-light-theme.png) ### 36. [安靜的燈光](https://marketplace.visualstudio.com/items?itemName=onecrayon.theme-quietlight-vsc) ![](https://github.com/onecrayon/theme-quietlight-vsc/raw/master/images/screenshot.png) ### 37. [跳燈](https://marketplace.visualstudio.com/items?itemName=bubersson.theme-hop-light) ![](https://raw.githubusercontent.com/bubersson/hop-theme-vscode/master/hop-light.png") ### 38. [NotepadPlusPlus Remixed](https://marketplace.visualstudio.com/items?itemName=sh4dow.theme-notepadplusplusremixed) ![](https://raw.githubusercontent.com/s-h-a-d-o-w/NotepadPlusPlus-Remixed-Theme/master/screenshot.png) ### 39. [GitHub Light](https://marketplace.visualstudio.com/items?itemName=Hyzeta.vscode-theme-github-light) ![](https://github.com/Hyzeta/vscode-theme-github-light/raw/master/screenshot/0.png) ### 40. [GitHub Plus](https://marketplace.visualstudio.com/items?itemName=thenikso.github-plus-theme) ![](https://github.com/thenikso/github-plus-theme/raw/master/screenshot.jpg) ## 多彩 厭倦了單色主題和沈悶的調色板?使用這些豐富多彩的主題為您的編輯器加入一些顏色。 您也可以透過安裝我們的[最佳彩色主題包](https://marketplace.visualstudio.com/items?itemName=thegeoffstevens.best-colorful-themes-pack)來安裝所有這些彩色主題。 ### 41. [紫色陰影](https://marketplace.visualstudio.com/items?itemName=ahmadawais.shades-of-purple) ![](https://raw.githubusercontent.com/ahmadawais/shades-of-purple-vscode/master/images/markdown.png) ### 42. [SynthWave](https://marketplace.visualstudio.com/items?itemName=RobbOwen.synthwave-vscode) ![](https://github.com/robb0wen/synthwave-vscode/raw/master/theme.jpg) ### 43. [藍色程式碼](https://marketplace.visualstudio.com/items?itemName=Sujan.code-blue) ![](https://i.imgur.com/JLCnwvi.jpg) ### 44. [賽博龐克](https://marketplace.visualstudio.com/items?itemName=max-SS.cyberpunk) ![](https://github.com/max-SS/cyberpunk/raw/master/assets/preview.png) ### 45. [LaserWave](https://marketplace.visualstudio.com/items?itemName=jaredkent.laserwave) ![](https://github.com/Jaredk3nt/laserwave/raw/master/screenshot.png) ### 46. [Zeonica](https://marketplace.visualstudio.com/items?itemName=andrewvallette.zeonica) ![](https://zeonicacom.files.wordpress.com/2018/09/zeonica_9502.png) ### 47. [潮人](https://marketplace.visualstudio.com/items?itemName=ModoNoob.vscode-hipster-theme) ![](https://github.com/ModoNoob/vscode-hipster-theme/raw/master/screenshot.png) ### 48. [野莓](https://marketplace.visualstudio.com/items?itemName=joebayer1340.wildberry-theme) ![](https://github.com/geoffstevens8/best-colorful-themes-pack/blob/master/images/wildberry.png?raw=true) ### 49. [Qiita](https://marketplace.visualstudio.com/items?itemName=Increments.qiita) ![](https://qiita-image-store.s3.amazonaws.com/0/6598/e054a4bb-cea1-8fc9-e193-fbb8376ed93d.png) ### 50. [軟體時代](https://marketplace.visualstudio.com/items?itemName=soft-aesthetic.soft-era-theme) ![](https://github.com/soft-aesthetic/soft-era-vs-code/raw/master/screenshot.png) ## 下一步是什麼? 想找更多主題?試試[搜尋 VS Code 市場](https://marketplace.visualstudio.com/search?target=VSCode&category=Themes&sortBy=Installs) 並依「主題」排序。開發人員已經建立了超過 2,500 個主題,您可以從中選擇來自訂 VS Code。 ___ 我喜歡建立讓開發人員滿意的工具。如果您喜歡這篇文章,您還應該查看我的其他專案: * [Code Time](https://www.software.com/code-time):自動程式設計指標和時間追蹤的免費擴展,就在您的編輯器中 * [SRC](https://www.software.com/src):在十分鐘或更短的時間內提供開發者世界的策略摘要 - 每週一次,直接發送到您的收件匣

使用 Services 和 RxJS 在 Angular 中進行簡單的狀態管理

原文出處:https://dev.to/avatsaev/simple-state-management-in-angular-with-only-services-and-rxjs-41p8 軟體開發中最具挑戰性的事情之一是狀態管理。目前,Angular 應用程式有幾個狀態管理庫:NGRX、NGXS、Akita...它們都有不同的狀態管理風格,最受歡迎的是 [NGRX](https://ngrx.io/),它幾乎遵循React 世界的FLUX/Redux 原則(基本上使用單向資料流和不可變資料結構,但使用RxJS可觀察流)。 但是,如果您不想學習、設定、使用整個狀態管理庫,也不想處理一個簡單專案的所有樣板文件,如果您只想使用您已經熟悉的工具來**管理狀態,該怎麼辦?Anular 開發人員 * *,並且仍然獲得狀態管理庫提供的效能最佳化和一致性(推送變更偵測,一種不可變資料流)。 **免責聲明:這不是針對狀態管理庫的帖子。我們確實在工作中使用NGRX,它確實幫助我們在非常大和複雜的應用程式中管理非常複雜的狀態,但正如我常說的,NGRX 使簡單應用程式的事情變得複雜,並簡化了複雜應用程式的事情,請記住這一點。* * 在這篇文章中,我將向您展示一種僅使用 **RxJS** 和 **依賴注入** 來管理狀態的簡單方法,我們所有的元件樹都將使用 OnPush 更改檢測策略。 想像我們有一個簡單的 Todo 應用程式,我們想要管理它的狀態,我們已經設定了元件,現在我們需要一個服務來管理狀態,讓我們建立一個簡單的 Angular 服務: ``` // todos-store.service.ts @Injectable({provideIn: 'root'}) export class TodosStoreService { } ``` 所以我們需要的是,一種提供待辦事項清單的方法,一種加入、刪除、過濾和完成待辦事項的方法,我們將使用 getter/setter 和 RxJS 的行為主題來執行此操作: 首先我們建立在 todos 中讀寫的方法: ``` // todos-store.service.ts @Injectable({provideIn: 'root'}) export class TodosStoreService { // - We set the initial state in BehaviorSubject's constructor // - Nobody outside the Store should have access to the BehaviorSubject // because it has the write rights // - Writing to state should be handled by specialized Store methods (ex: addTodo, removeTodo, etc) // - Create one BehaviorSubject per store entity, for example if you have TodoGroups // create a new BehaviorSubject for it, as well as the observable$, and getters/setters private readonly _todos = new BehaviorSubject<Todo[]>([]); // Expose the observable$ part of the _todos subject (read only stream) readonly todos$ = this._todos.asObservable(); // the getter will return the last value emitted in _todos subject get todos(): Todo[] { return this._todos.getValue(); } // assigning a value to this.todos will push it onto the observable // and down to all of its subsribers (ex: this.todos = []) private set todos(val: Todo[]) { this._todos.next(val); } addTodo(title: string) { // we assaign a new copy of todos by adding a new todo to it // with automatically assigned ID ( don't do this at home, use uuid() ) this.todos = [ ...this.todos, {id: this.todos.length + 1, title, isCompleted: false} ]; } removeTodo(id: number) { this.todos = this.todos.filter(todo => todo.id !== id); } } ``` 現在讓我們建立一個方法來設定待辦事項的完成狀態: ``` // todos-store.service.ts setCompleted(id: number, isCompleted: boolean) { let todo = this.todos.find(todo => todo.id === id); if(todo) { // we need to make a new copy of todos array, and the todo as well // remember, our state must always remain immutable // otherwise, on push change detection won't work, and won't update its view const index = this.todos.indexOf(todo); this.todos[index] = { ...todo, isCompleted } this.todos = [...this.todos]; } } ``` 最後是一個可觀察的來源,它將只為我們提供已完成的待辦事項: ``` // todos-store.service.ts // we'll compose the todos$ observable with map operator to create a stream of only completed todos readonly completedTodos$ = this.todos$.pipe( map(todos => todos.filter(todo => todo.isCompleted)) ) ``` 現在,我們的待辦事項商店看起來像這樣: ``` // todos-store.service.ts @Injectable({providedIn: 'root'}) export class TodosStoreService { // - We set the initial state in BehaviorSubject's constructor // - Nobody outside the Store should have access to the BehaviorSubject // because it has the write rights // - Writing to state should be handled by specialized Store methods (ex: addTodo, removeTodo, etc) // - Create one BehaviorSubject per store entity, for example if you have TodoGroups // create a new BehaviorSubject for it, as well as the observable$, and getters/setters private readonly _todos = new BehaviorSubject<Todo[]>([]); // Expose the observable$ part of the _todos subject (read only stream) readonly todos$ = this._todos.asObservable(); // we'll compose the todos$ observable with map operator to create a stream of only completed todos readonly completedTodos$ = this.todos$.pipe( map(todos => todos.filter(todo => todo.isCompleted)) ) // the getter will return the last value emitted in _todos subject get todos(): Todo[] { return this._todos.getValue(); } // assigning a value to this.todos will push it onto the observable // and down to all of its subsribers (ex: this.todos = []) private set todos(val: Todo[]) { this._todos.next(val); } addTodo(title: string) { // we assaign a new copy of todos by adding a new todo to it // with automatically assigned ID ( don't do this at home, use uuid() ) this.todos = [ ...this.todos, {id: this.todos.length + 1, title, isCompleted: false} ]; } removeTodo(id: number) { this.todos = this.todos.filter(todo => todo.id !== id); } setCompleted(id: number, isCompleted: boolean) { let todo = this.todos.find(todo => todo.id === id); if(todo) { // we need to make a new copy of todos array, and the todo as well // remember, our state must always remain immutable // otherwise, on push change detection won't work, and won't update its view const index = this.todos.indexOf(todo); this.todos[index] = { ...todo, isCompleted } this.todos = [...this.todos]; } } } ``` 現在我們的智慧元件可以存取商店並輕鬆操作它: *(PS:我建議使用[ImmutableJS](https://immutable-js.github.io/immutable-js)而不是手動管理不變性)* ``` // app.component.ts export class AppComponent { constructor(public todosStore: TodosStoreService) {} } ``` ``` <!-- app.component.html --> <div class="all-todos"> <p>All todos</p> <app-todo *ngFor="let todo of todosStore.todos$ | async" [todo]="todo" (complete)="todosStore.setCompleted(todo.id, $event)" (remove)="todosStore.removeTodo($event)" ></app-todo> </div> ``` 這是完整的最終結果: [StackBlitz 上具有真實 REST API 的完整範例](https://stackblitz.com/edit/angular-rxjs-store?file=src%2Fapp%2Ftodos-store.service.ts) 這也是一種可擴展的狀態管理方式,您可以使用Angular 強大的DI 系統輕鬆地將其他儲存服務注入彼此,將其可觀察量與管道運算符結合起來以建立更複雜的可觀察量,並注入HttpClient 等服務以從伺服器提取資料例如。無需所有 NGRX 樣板或安裝其他狀態管理庫。盡可能保持簡單和輕鬆。 --- 在 Twitter 上關注我,以了解更多有趣的 Angular 相關內容:https://twitter.com/avatsaev

如何從頭開始為基本網站設定 Webpack 4 或 5

原文出處:https://dev.to/antonmelnyk/how-to-configure-webpack-from-scratch-for-a-basic-website-46a5 #簡介 你好,讀者! 如您所知,設定 [Webpack](https://webpack.js.org/) 可能是一項令人沮喪的任務。儘管有很好的文件,但由於一些原因,這個捆綁器並不是一匹舒服的馬。 Webpack 團隊正在非常努力且相對快速地開發它,這是一件好事。然而,對於新開發人員來說,一次性學習所有內容是難以承受的。教程已經過時,一些插件損壞,發現的範例可能會令人困惑。有時,您可能會陷入一些瑣碎的事情,並透過 Google 進行大量搜尋,以在 GitHub issues 中找到一些最終有幫助的簡訊。 缺乏關於 Webpack 及其工作原理的介紹性文章,人們直接奔向 *create-react-app* 或 *vue-cli* 等工具,但有時需要編寫一些簡單的純 JavaScript 和 SASS,無需框架或任何奇特的東西。 本指南將逐步介紹 ES6、SASS 和圖片/字體的 Webpack 配置,無需任何框架。對於大多數簡單的網站開始使用 Webpack 或將其用作進一步學習的平台應該足夠了。儘管本指南需要一些有關 Web 開發和 JavaScript 的先驗知識,但它可能對某些人有用。至少當我開始使用 Webpack 時,我會很高興遇到這樣的事情! #我們的目標 我們將使用 Webpack 將 JavaScript、樣式、圖像和字體檔案捆綁到一個 *dist* 資料夾中。 ![](https://thepracticaldev.s3.amazonaws.com/i/9n9g3yr1v6lhjfg8roqc.png) Webpack 將產生 1 個捆綁的 JavaScript 檔案和 1 個捆綁的 CSS 檔案。您可以像這樣簡單地將它們加入到 HTML 文件中(當然,如果需要,您應該更改 dist 資料夾的路徑): ``` <link rel="stylesheet" href="dist/bundle.css"> <script src="dist/bundle.js"></script> ``` 你就可以走了:tropical_drink: 您可以查看本指南中完成的範例::link:[link](https://github.com/heyanton/simple_webpack_boilerplate)。 注意:我最近更新了依賴項。本指南適用於最新的 Webpack 5,但設定仍適用於 Webpack 4,以防您需要! #開始 ####1。安裝Webpack 我們使用 [npm](https://www.npmjs.com/): `$ npm init` 命令在專案資料夾中建立一個 *package.json* 文件,我們將在其中放置 JavaScript 依賴項。然後我們可以使用 $ npm i --save-dev webpack webpack-cli 來安裝 Webpack 本身。 ####2。建立入口點文件 Webpack 從單一 JavaScript 檔案開始運作,該檔案稱為入口點。在 *javascript* 資料夾中建立 *index.js*。您可以在這裡編寫一些簡單的程式碼,例如 `console.log('Hi')` 以確保其正常運作。 ![](https://thepracticaldev.s3.amazonaws.com/i/tlt38mn2u385i06y4vzn.png) ####3。建立 webpack.config.js ....在專案資料夾中。這裡是所有:sparkles:魔法發生的地方。 ``` // Webpack uses this to work with directories const path = require('path'); // This is the main configuration object. // Here, you write different options and tell Webpack what to do module.exports = { // Path to your entry point. From this file Webpack will begin its work entry: './src/javascript/index.js', // Path and filename of your result bundle. // Webpack will bundle all JavaScript into this file output: { path: path.resolve(__dirname, 'dist'), publicPath: '', filename: 'bundle.js' }, // Default mode for Webpack is production. // Depending on mode Webpack will apply different things // on the final bundle. For now, we don't need production's JavaScript // minifying and other things, so let's set mode to development mode: 'development' }; ``` ####4。在 *package.json* 中新增 npm 腳本來執行 Webpack 要執行 Webpack,我們必須使用 npm 腳本和簡單的命令「webpack」以及我們的設定檔作為 *config* 選項。我們的 *package.json* 現在應該如下所示: ``` { "scripts": { "build": "webpack --config webpack.config.js" }, "devDependencies": { "webpack": "^4.29.6", "webpack-cli": "^3.2.3" } } ``` ####5。執行Webpack 透過基本設置,您可以執行“$ npm run build”命令。 Webpack 將尋找我們的入口文件,解析其中的所有[*import*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 模組相依性並將其捆綁到*dist* 資料夾中的單一 *.js* 檔案。在控制台中,您應該看到如下內容: ![](https://thepracticaldev.s3.amazonaws.com/i/8j9rn72esbfbvn27duz6.png) 如果您將 `<script src="dist/bundle.js"></script>` 加入到 HTML 檔案中,您應該在瀏覽器控制台中看到「Hi」! #:顯微鏡:裝載機 偉大的!我們捆綁了標準 JavaScript。但是,如果我們想使用 ES6(及更高版本)的所有酷炫功能並保持瀏覽器相容性怎麼辦?我們應該如何告訴 Webpack 將我們的 ES6 程式碼轉換(*transpile*)相容於瀏覽器的程式碼? 這就是 Webpack **loaders** 發揮作用的地方。 Loader 是 Webpack 的主要功能之一。他們對我們的程式碼進行某些轉換。 讓我們在 *webpack.config.js* 檔案中新增選項 *module.rules*。在此選項中,我們將介紹 Webpack 如何準確地轉換不同類型的檔案。 ``` entry: /* ... */, output: /* ... */, module: { rules: [ ] } ``` 對於 JavaScript 文件,我們將使用: ###1。 [babel-loader](https://github.com/babel/babel-loader) [Babel](https://babeljs.io/) 是目前最好的 JavaScript 轉譯器。我們將告訴 Webpack 在捆綁之前使用它將現代 JavaScript 程式碼轉換為與瀏覽器相容的 JavaScript 程式碼。 Babel-loader 正是這樣做的。讓我們安裝它: `$ npm i --save-dev babel-loader @babel/core @babel/preset-env` 現在我們要新增有關 JavaScript 檔案的規則: ``` rules: [ { test: /\.js$/, exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] ``` * `test` 是我們要轉換的檔案副檔名的[正規表示式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions)。在我們的例子中,它是 JavaScript 檔案。 * `exclude` 是一個正規表示式,它告訴 Webpack 在轉換模組時應該忽略哪個路徑。這意味著如果我們將來導入它們,我們將不會轉換從 npm 匯入的供應商庫。 * `use` 是主要規則的選項。這裡我們設定 loader,它將應用於與「test」正規表示式相對應的檔案(在本例中為 JavaScript 檔案) * “選項”可能因載入程式而異。在這種情況下,我們為 Babel 設定預設預設,以考慮應該轉換哪些 ES6 功能,哪些不應該轉換。它本身是一個單獨的主題,如果您感興趣,可以深入研究它,但目前保持這樣是安全的。 現在您可以將 ES6 程式碼安全地放置在 JavaScript 模組中! ###2。 [sass-loader](https://github.com/webpack-contrib/sass-loader) 是時候處理樣式了。通常,我們不想寫純 CSS。我們經常使用 [SASS](https://sass-lang.com/) 預處理器。我們將 SASS 轉換為 CSS,然後應用自動前綴和縮小。這是 CSS 的一種「預設」方法。讓我們告訴 Webpack 要這樣做。 假設我們在 *javascripts/index.js* 入口點導入主 SASS 檔案 *sass/styles.scss*。 ``` import '../sass/styles.scss'; ``` 但目前,Webpack 不知道如何處理 *.scss* 檔案或除 *.js* 之外的任何檔案。我們需要新增適當的載入器,以便 Webpack 可以解析這些檔案: `$ npm i --save-dev sass sass-loader postcss-loader css-loader` 我們可以為 SASS 檔案新增規則並告訴 Webpack 如何處理它們: ``` rules: [ { test: /\.js$/, /* ... */ }, { // Apply rule for .sass, .scss or .css files test: /\.(sa|sc|c)ss$/, // Set loaders to transform files. // Loaders are applying from right to left(!) // The first loader will be applied after others use: [ { // This loader resolves url() and @imports inside CSS loader: "css-loader", }, { // Then we apply postCSS fixes like autoprefixer and minifying loader: "postcss-loader" }, { // First we transform SASS to standard CSS loader: "sass-loader" options: { implementation: require("sass") } } ] } ] ``` 請注意此處有關 Webpack 的重要事項。可連結多個裝載機;它們將從“use”陣列中的最後一個到第一個逐個應用。 現在,當 Webpack 在程式碼中遇到“import 'file.scss';”時,它知道該怎麼做! ####PostCSS 我們應該如何告訴 *postcss-loader* 它必須應用哪些轉換?我們建立一個單獨的設定檔 `postcss.config.js` 並使用我們的樣式所需的 postcss 外掛程式。您可能會發現對最基本和有用的插件進行縮小和自動加入前綴,以確保 CSS 為您的真實網站做好準備。 首先,安裝這些 postcss 外掛:`$ npm i --save-dev autoprefixer cssnano`。 其次,將它們加入到 *postcss.config.js* 檔案中,如下所示: ``` module.exports = { plugins: [ require('autoprefixer'), require('cssnano'), // More postCSS modules here if needed ] } ``` 您可以更深入地研究 [PostCSS](https://github.com/postcss/postcss),找到更多適合您的工作流程或專案需求的插件。 在完成所有 CSS 設定之後,只剩下一件事了。 Webpack 將解析您的 *.scss* 導入,對其進行轉換,然後...下一步是什麼?它不會神奇地建立一個與您的樣式捆綁在一起的單一 *.css* 檔案;我們必須告訴 Webpack 要這樣做。但這個任務超出了裝載機的能力範圍。為此,我們必須使用 Webpack 的 **插件**。 #:electric_plug: 插件 他們的目的是做裝載機做不到的任何事。如果我們需要將所有轉換後的 CSS 提取到一個單獨的「捆綁」檔案中,我們必須使用插件。我們的案例有一個特殊的插件:*MiniCssExtractPlugin*: `$ npm i --save-dev mini-css-extract-plugin` 我們可以在 *webpack.config.js* 檔案的開頭單獨導入外掛: ``` const MiniCssExtractPlugin = require("mini-css-extract-plugin"); ``` 在我們設定載入器的“module.rules”陣列之後加入新的“plugins”程式碼,我們用選項啟動我們的插件: ``` module: { rules: [ /* ... */ ] }, plugins: [ new MiniCssExtractPlugin({ filename: "bundle.css" }) ] ``` 現在我們可以將此插件連結到我們的 CSS 載入器中: ``` { test: /\.(sa|sc|c)ss$/, use: [ { // After all CSS loaders, we use a plugin to do its work. // It gets all transformed CSS and extracts it into separate // single bundled file loader: MiniCssExtractPlugin.loader }, { loader: "css-loader", }, /* ... Other loaders ... */ ] } ``` 完畢!如果您按照步驟操作,則可以執行「$ npm run build」命令並在 *dist* 資料夾中找到 *bundle.css* 檔案。現在的一般設定應如下所示: ![](https://thepracticaldev.s3.amazonaws.com/i/ye1wwk5r7rvxcu9n0ylm.png) Webpack 有大量用於不同目的的插件。您可以根據需要在[官方文件](https://webpack.js.org/plugins/)中探索它們。 # 更多載入器:圖像和字體 此時,您應該了解 Webpack 工作原理的基礎知識。但我們還沒完成。大多數網站都需要一些資源:我們透過 CSS 設定的圖像和字體。由於 *css-loader*,Webpack 可以解析 `background-image: url(...)` 行,但它不知道如果將 URL 設為 *.png* 或 *jpg* 檔案該怎麼辦。 我們需要一個新的載入器來處理 CSS 內的檔案或能夠直接在 JavaScript 中匯入它們。這是: ### [檔案載入器](https://github.com/webpack-contrib/file-loader) 使用 `$ npm i --save-dev file-loader` 安裝它,並在我們的 *webpack.config.js* 新增規則: ``` rules: [ { test: /\.js$/, /* ... */ }, { test: /\.(sa|sc|c)ss$/, /* ... */ }, { // Now we apply rule for images test: /\.(png|jpe?g|gif|svg)$/, use: [ { // Using file-loader for these files loader: "file-loader", // In options we can set different things like format // and directory to save options: { outputPath: 'images' } } ] } ] ``` 現在,如果你在 CSS 中使用一些像這樣的圖像: ``` body { background-image: url('../images/cat.jpg'); } ``` Webpack 將成功解決它。您將在 *dist/images* 資料夾中找到帶有雜湊名稱的圖像。在 *bundle.css* 會發現類似這樣的內容: ``` body { background-image: url(images/e1d5874c81ec7d690e1de0cadb0d3b8b.jpg); } ``` 正如你所看到的,Webpack 非常聰明——它正確解析了你的 url 相對於 *dist* 資料夾的路徑! 您也可以為字體加入規則並以與圖像類似的方式解析它們;將 outputPath 變更為 *fonts* 資料夾以保持一致性: ``` rules: [ { test: /\.js$/, /* ... */ }, { test: /\.(sa|sc|c)ss$/, /* ... */ }, { test: /\.(png|jpe?g|gif|svg)$/, /* ... */ }, { // Apply rule for fonts files test: /\.(woff|woff2|ttf|otf|eot)$/, use: [ { // Using file-loader too loader: "file-loader", options: { outputPath: 'fonts' } } ] } ] ``` #總結 就是這樣!經典網站的簡單 Webpack 設定。我們介紹了 **入口點**、**載入器** 和 **插件** 的概念以及 Webpack 如何轉換和捆綁檔案。 當然,這是一個非常簡單的配置,旨在了解 Webpack 的一般概念。如果您需要的話,還有很多事情可以加入:來源映射、熱重載、設定 JavaScript 框架以及 Webpack 可以做的所有其他事情,但我覺得這些事情超出了本指南的範圍。 如果您遇到困難或想了解更多訊息,我鼓勵您查看 Webpack [官方文件](https://webpack.js.org/concepts)。快樂捆綁!

身為開發者,我的 8 個讓生活更美好的秘訣

原文出處:https://dev.to/wraith/my-8-tips-for-a-better-life-as-a-developer-1hfg 我擔任軟體開發人員和工程師已經有 8 年多一點了,從我自己的經驗以及從一些非常有才華的人那裡學到了很多東西。在這篇文章中,我想分享一些真正讓我的體驗變得更好、更愉快的事情。有些是技術性的,有些只是一般生活技巧。但所有這些都改善了我在軟體開發方面的生活和經驗,希望透過分享這些課程和技巧,我可以幫助您避免一些我為了弄清楚它們而必須經歷的不愉快的時光。 ## 1. 找一個您喜歡工作的地方 ![三個人坐在咖啡店裡用電腦工作,微笑。](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/shosztzzfmpjuf7ksr5c.jpg) 您的環境對您的生活貢獻很大。它可以增加或減輕壓力,幫助您集中註意力或分散注意力,讓您感到安全或不安全等等。因為它在我們每個人的生活中都扮演著不可或缺的角色,所以我認為從這裡開始是合適的。 無論您是在辦公室還是遠端工作,您很可能可以採取一些措施來找到一個讓您感覺「合適」的地方。我說「對」是因為這裡每個人都會有所不同。有些人想要感到舒適和「賓至如歸」。其他人想要一個不太舒適的區域,而是真正讓他們「進入狀態」並集中註意力的區域。 多年來,我嘗試了很多不同的地點,只是為了看看什麼對我有用。我坐在陽台上,享受早晨涼爽的空氣,喝著一杯熱咖啡。我確實坐在桌子底下,身上蓋著毯子。我坐在壁櫥、角落、咖啡店、餐廳、酒吧、汽車、公園、餐桌和樓梯井裡。透過所有這些實驗,我已經能夠找到在我需要時為我提供所需的地方。如果我需要集中註意力,我就需要獨處。某處有一扇可以關閉的門,但沒有窗戶讓我注意到有人走過。當我太舒適時,就像依偎在柔軟的沙發上的毯子裡時,我的工作效果就不太好。如果我需要改變節奏,或者只是需要和人們在一起,我發現我真的很喜歡坐在不太擁擠的小酒吧或餐廳裡。我可以在某個地方點一杯飲料和一份開胃菜然後工作,但周圍仍然有幾個人。 所以我鼓勵你嘗試幾個地方。找出什麼對你有用,同樣重要的是,找出什麼對你沒用。如果你找不到地方,你總是可以花一點力氣去打造你想要的地方! 「正確」對你來說意味著什麼? ## 2. 投資硬件 ![黑暗房間裡一張配有高科技設備的桌子,LED 照亮空間](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7knnfnk29bpe02gibac4.png) 作為軟體開發人員,我們使用的硬體數量非常多。可以說,我們使用鍵盤和辦公椅之類的東西比生活中任何其他物品都多。當然,我們可以使用任何舊鍵盤來完成工作,我們可以坐在任何椅子上。但我發現,對「更好」的硬體進行一點投資會對我的工作體驗產生很大的影響。 ### 椅子 如果您在工作時坐著,並且您只想投資一件東西,那麼它絕對應該是您的椅子。一張既提供舒適又提供支撐的椅子確實可以大有幫助。從您可以坐多久並集中註意力而不會感到不舒服,到日常生活中背部、頸部和肩膀的感覺,您的椅子對您的整體健康和福祉有很大影響。因此,一定要找到一款好的產品,而不要只滿足於會導致不良姿勢的產品。 我個人使用 [Secretlab Titan Evo(蝙蝠俠主題)](https://secretlabchairs.ca/products/titan-evo-2022-series?sku=R22PU-Batman),幾年來我對它非常滿意。與許多高端桌椅相比,價格還不錯。 ### 鍵盤 僅次於椅子(但相差不多)的是鍵盤。輕鬆地成為我們每天工作中互動最多的工具。那裡也有很多選擇,因此無論您的個人喜好如何,很可能有一些東西可以滿足您的需求。 每個人選擇合適的鍵盤都有很大不同。有些人喜歡低調的鑰匙而不是機械鑰匙。有些人需要整合 USB 連接埠。成本、人體工學、有線或無線、可自訂的按鍵和開關、背光、可配置的 LED、支援配置按鍵佈局、高度和大小、按鍵數量,這樣的例子不勝枚舉。尋找適合您的鍵盤無疑是一段旅程,但我強烈建議您繼續下去。當然,我們可以使用任何鍵盤來完成我們的工作......但我保證,如果您嘗試一下,找到「正確的」鍵盤將使您作為開發人員的一天和體驗更加愉快。 我使用 [Moonlander Mark 1](https://www.zsa.io/moonlander/),絕對💙它!分離式設計確實幫助我不再那麼無精打采,也幫助消除了我長期以來的肩膀和手腕疼痛。再加上那些櫻桃棕色的開關聽起來很漂亮😍! ### 老鼠 談論鍵盤就不能不談論它們的助手——滑鼠。就像鍵盤一樣,市面上有許多不同類型的鍵盤,每個人都會有自己的偏好。幸運的是,即使是半像樣的滑鼠也有相當低的價格,因此嘗試一些滑鼠來找到適合您的滑鼠相對容易。但與此處的所有其他項目一樣,投入一點時間和金錢即可對您的體驗產生積極影響。 我的老鼠是 [ZLOT 垂直遊戲滑鼠](https://www.amazon.com/gp/product/B07T3PFWCB?th=1)。它是一款較輕(重量)的滑鼠,但具有良好的人體工學感覺和響應能力,我已經喜歡了很長一段時間了。 ### 監視器 這絕對是一個可選項目,但我發現它讓我的工作更加愉快。並非每個人都需要外接顯示器。有些人實際上更喜歡直接使用筆記型電腦工作。但如果您確實喜歡使用外部顯示器,這是一項可以產生巨大影響的投資。 遺憾的是,由於多台 4k 顯示器在 Mac 上工作出現問題,我放棄了多顯示器設置,現在使用 [Sceptre 35" 曲面顯示器](https://www.sceptre.com/Monitors/2K-4K -Series /C355W-3440UN-35-Curved-Monitor-product1176category12category98.html)。它有很多空間,所以我仍然可以在一個螢幕上打開大量視窗。 ### 耳機 耳機也是可選的(有些人可能會反對這一點😝),但它們的好處怎麼強調都不為過。從減少干擾到幫助您集中註意力,一副好的耳機可以大有幫助。就像我列出的大多數項目一樣,每個人的偏好都會有所不同。但是,投入一點時間和金錢來尋找一雙適合您的好鞋,確實可以將您的遊戲提升到一個新的水平。我認識的許多人都尋求良好的降噪效果,而且它們必須輕盈舒適,這樣才能一次佩戴幾個小時。 我個人喜歡使用 Beats。我曾經使用[Studio3](https://www.bestbuy.ca/en-ca/product/beats-by-dr-dre-studio3-over-ear-noise-cancelling-bluetooth-headphones-black/11534527 )但是當我必須開始戴眼鏡時,我不喜歡這些耳機給我的鏡框帶來的壓力,所以我改用了[Beats Fit Pro](https://www.beatsbydre.com/earbuds/beats- fit- pro?sku=MK2F3) 並且對它們非常滿意。我已經連續戴了 8 個小時,效果非常好。它們輕巧、舒適、音質好,並且在我慢跑和運動時表現良好且穩定。 您使用什麼硬體?您夢想的硬體是什麼? ## 3. 找到您*喜歡*使用的工具 ![應用程式牆的螢幕截圖,應用程式圖示上有有趣的表情符號臉孔](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clgqlfokffwpu57wnkr9.png) 除了硬體之外,作為開發人員,我們還使用許多軟體工具來完成我們的工作。有些我們別無選擇,但也有很多我們可以選擇,找到您真正喜歡使用的工具確實可以讓您的日常體驗變得更好。即使只是擁有一個可以配置為您喜歡的外觀的工具也可以產生積極的影響。 我在這裡想強調的不是找到每個人都使用的工具,因為他們可以做各種各樣的事情。更多的是尋找您真正「喜歡」和「期待」使用的工具來完成工作。即使它們不能完成其他工具可以完成的所有奇特的事情,如果您確實希望使用其他工具,那就使用它!擁有我們積極喜歡的工具確實會為我們的生活增添很多積極性。 多年來,類似的工具有很多,但這裡有一些工具為我的日常生活帶來了很多樂趣: - Giphy 桌面應用程式 - 用 gif 回覆取代無聊的文字,讓 Slack 訊息變得生動起來。 - 光線投射 - 這已經取代了我 Mac 上的 Spotlight。透過專業版,我可以存取 ChatGPT 4...因此,只需一個快速鍵盤快捷鍵,我就能輕鬆掌握 AI。對我來說遊戲規則改變者! - 黑曜石 - 雖然這已經是一個流行的筆記應用程序,但我花了一些時間編寫了一些腳本來為我自動化工作,它完全改變了我記下所有筆記並跟踪我需要做的所有事情的方式。 - 弧形瀏覽器 - Arc 花了整整 1 天的時間才成為我的主要瀏覽器。現在,當我測試瀏覽器對我正在建立的某些功能的支援時,我只使用其他瀏覽器(在我的桌面上)。 - 習慣性的 - 獲得徽章、成就和一般遊戲化讓我非常有動力,所以這個待辦事項應用程式讓我管理和執行任務變得更加有趣! 有哪些工具可以為您的日常開發生活帶來樂趣? ## 4. 設定目標 ![一台打字機,上面印有一張伸出的紙上的「目標」](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vcsabk75mtb2o8dlwbt3.jpg) 我知道這聽起來很明顯,而且我相信我們都從無數其他來源聽到這一點。但您可能會驚訝地發現有多少人沒有為自己設定目標。不相信我?向你的任意 2 到 3 個鄰居詢問他們目前正在努力實現什麼目標。當我問這個問題時,經常得到的只是聳聳肩,然後回答「沒什麼」。 僅僅設定目標也是不夠的。你也必須定期考慮它們。有些方法建議將它們寫下來並放在鏡子上或您經常看到它們的地方。這個方法對我個人來說沒有效果,但也許對你有用?對我來說有效的方法是每天早上開始工作前坐下來15 分鐘,並重點思考我的目標、我所有的待辦事項以及日曆上的所有事情(是的,我實際上在日曆上留出15分鐘的時間)這個,並強迫自己堅持這個時間)。在這段時間裡,我思考我的目標,並找出我今天可以做的一件小事,讓我離實現每個目標更近一步。 例如,如果我的目標是在家人來過感恩節之前清理車庫,我會想,「我今天可以做哪一件小事來實現這個目標?」。有時答案特別小…「掃到工作台下面」。其他時候我可能會更有動力,或者我有更多的可用時間,這可能是更大的事情。無論如何,請花一些時間考慮您今天可以採取的一項行動來實現該目標。 當我這樣做時,我的大腦中會發生一些事情。我發現自己感覺更有成就感和更樂觀。當然,完成目標可能是一條漫長的道路(如果它是一個大目標),但是知道我離我想要完成的事情更近了,這對我的日常生活產生了積極的影響,並讓我能夠完成的事情比我想像的還要多。 無論大小,給自己設定目標。然後定期思考它們,並採取許多微小的行動,以朝著前進邁出一步。我保證這會為您的生活帶來美好的事物! 現在您正在努力實現哪些目標? ## 5. 保持好奇心並了解*為什麼* ![視窗上有一個標誌,上面寫著「#becurious」](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/42zs3m4tlzbcilh338nd.png) 很多人對編碼專案中的完成方式感到沮喪或評判。我肯定去過那裡! - “為什麼有人選擇這項技術?!對於這個用例來說,其他技術要好得多...” - “為什麼有人會寫這樣的程式碼?!” - “如果我們不做 X 而只是做…,事情會好得多” 這些聽起來很熟悉嗎? 儘管事情有時會令人沮喪,但在軟體開發中,做出的每個決定背後幾乎總是有一個「原因」。這是最好的選擇嗎?也許不是……但做出這樣的選擇還是有原因的。 我曾經對事情的現狀感到沮喪,然後在嘗試解決問題時感到沮喪,然後在遇到障礙時感到沮喪。但最終,事情突然發生了,我沒有感到沮喪,而是開始尋找這些事情發生的原因。背後的*原因*是什麼。當我養成「尋找原因」而不是「想知道為什麼不」的習慣時,我的好奇心變得更強。我發現我正在尋找更多的信息,更徹底地學習和理解事物,更多地同情與我一起工作的人,最終,沮喪的感覺減少了很多。 現在,我的經歷更加積極了。無論我是重構一段複雜的程式碼,試圖找到解決惱人問題的方法,還是為新工作學習全新的程式碼庫,我實際上更喜歡這個過程,因為我只是好奇並想知道「為什麼」。 最近一次讓您真正感到沮喪的編碼*事情*是什麼?您知道*為什麼*會是這樣嗎? ## 6. 為重點工作劃出日曆 ![一週中每天 2 小時的日曆條目顯示「焦點時間」](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/13x844h65h98y6c78bsk.png) 這說起來容易做起來難,具體取決於您的工作地點,但它會對您的開發人員生活產生驚人的影響! 您是否曾經在該區域中,只是編寫程式碼來建立該新功能,然後「*叮!*」有人向您發送了一條緊急的 Slack 訊息?或是有人拍拍你的肩膀問你問題?您解決了乾擾問題,然後返回螢幕,然後您就失去了所有註意力?如果沒有……我願意賭很多錢,你會在職業生涯的某個時刻這麼做。 「在區域中」或進入「心流」的概念是一個已經被研究和寫了很多的主題。我強烈建議您查看一些有關該主題的文章和書籍,因為這是一個非常有趣的主題(至少對我來說是😃)!其中許多研究都表明,處於心流狀態是多麼有益,而且在中斷後可能需要 20 分鐘以上才能恢復到那種精神狀態!因此,找到讓自己進入這種心態並保持這種狀態的方法非常重要! 我發現讓自己進入這種狀態的最佳方法之一就是在日曆上劃出大量時間專門用於「專注工作」。一開始這可能是一個挑戰,讓人們在嘗試聯繫之前檢查您的日曆或 Slack 狀態,並幫助每個人了解您將在焦點時間結束後立即回覆他們。但最終人們會明白過來,並且回報是巨大的!別忘了在這段時間關閉通知! 不過這裡有一些提示...... - 接受有時會出現緊急事務並需要更高優先順序的事實。這就是生活,我們只能隨波逐流……但這不該成為「常態」。 - 拍攝 2-3 小時的片段。少於這個數量會讓人覺得不夠,但超過這個數量,人們就會被迫更頻繁地打斷你。請記住,其他人也有重要、緊急的事務,在當今的工作環境下,讓他們等待半天以上才能獲得地址確實不公平或不合理。 - 在你最有生產力的時間安排這些時間段。對我來說,早上 6 點到 10:30 左右我的工作效率最高。所以我通常會嘗試將我的專注時間安排在這些時間裡。 您發現一天中的什麼時段您的工作效率最高? ## 7. 保持 PR 較小 ![GitHub 審核標題的螢幕截圖,顯示 3 個檔案已更改,總共進行了 35 項更改](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jf7cy31tmzjjfz9wz2cn.png) 我喜歡這個,並且在過去一年左右的時間裡它已經成為我的首要任務。 事實證明,保持 Pull 請求(或 GitLab 人員的合併請求)較小有很多積極的好處。發布的錯誤更少,我們審查程式碼的時間更少,功能的推出速度更快,僅舉幾例。所有這些不僅使我們的產品變得更好,而且我發現它也極大地改善了我作為開發人員的體驗! 透過專注於較小的變化,我發現我可以更徹底地思考問題,考慮到在較大變化的混亂中可能被忽視的用例。我能夠更快地將變更納入審查,我的團隊成員能夠更快地審查我的程式碼,因為我只佔用了他們5 分鐘而不是2 小時的時間,並且在審查期間,我收到的程式碼要少得多變更請求。因此,更好的程式碼將會出現,我可以繼續花更多的時間來建立新的東西,而不是必須解決一堆被遺漏的錯誤。 另一方面,審查小型 PR|MR 比大型 PR|MR 更令人愉悅。您是否曾經需要審查某人的 PR|MR,其中包含數千個更改、跨越 20 多個檔案以及應用程式的多個區域?當你這樣做時,你的第一個反應是什麼?您是否對參與並開始審核感到過於興奮?或者,也許您感到“呃”,於是推遲了會議,因為距離下一次會議只有 30 分鐘,而您可以在這段時間內完成其他事情? 當審查大型 PR|MR 時,通常會失去很多細節(或至少受到較少的關注),最終,大多數人會達到「審查盲目性」或「審查疲勞」的地步,事情開始被忽視,或者審稿人必須離開一段時間,稍後再回來。這一切都會導致審核過程花費更長的時間、效率更低,並導致提交更多的變更請求。更不用說所有團隊成員都有的不滿情緒了。 自從我開始將此作為自己的優先事項,並與團隊成員一起努力讓他們也這樣做時,我注意到我在 PR|MR 方面的經驗明顯改善了。我更願意在會議之間跳出一些評論,我不得不要求更少的改變,而且我不會在需要離開並重新振作起來之後感到精疲力盡。就連我的計劃也變得更準確了! 總而言之,我強烈向大家推薦這個。如果您想了解更多關於這樣做的好處,我建議您查看 [LinearB 部落格](https://linearb.io/blog) 以及 [Dev Interrupted 播客](https://linearb .io/dev-interrupted/ podcast).他們談到了一些很棒的觀點,我發現這些觀點確實對工程領導者和團隊有幫助! 你曾經審查過的最糟糕的公關是什麼? ## 8. 寫下一切! ![有人在筆記本上寫筆記](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yeqff10xv6ej22xk22w8.jpg) 我的最後一個建議是我在去年開始做的事情,在閱讀了[如何做智慧筆記](https://www.amazon.ca/How-Take-Smart-Notes-Technique/dp/3982438802)和[把事情做好](https://www.amazon.ca/Getting-Things-Done-Stress-Free-Productivity/dp/0142000280)它對我的生活產生了驚人的影響。 當我學到新東西時,我會把它寫下來。即使只是一小段描述我學到的東西。當出現新任務時,無論大小,我都會把它寫下來。在會議期間,如果分享想法、給予回饋、提出問題,所有這些都會被記錄下來。如果我對某事有一個隨意的想法,或者一個頭腦發熱的想法……你猜對了……它會被寫下來。然後,每當我有幾分鐘空閒時間時,我都會先看筆記,而不是瀏覽社群媒體。我盡可能回顧它們,這強化了我腦海中的信息,但也幫助我將不同的想法聯繫在一起,這往往會產生一個全新的想法。 透過這樣做,我發現我對事情的記憶更加徹底。如果我不能,我有記錄並且可以將其調出!它使我能夠完成更多的工作文檔,而且我甚至在任何給定時間都有 4 或 5 篇部落格文章正在編寫中!遺漏的事情少了很多,而且我能夠完成更多的事情。 我最近開始了一份新工作,透過使用這種方法,人們已經來找我詢問我是如何做到這麼多的!秘密醬汁?全部寫下來並將其添加到系統中。 這對我來說改變了遊戲規則,我只需要鼓勵其他人也這樣做,因為我真的相信這可以使他們的生活受益匪淺! 你用什麼方法來記住和分享你學到的東西? ## 結論 在過去 8 年多的軟體開發人員和工程師工作中,我學到了很多。我經歷過好時光和壞時光,並一路走來學到了一些非常有用的人生課程。透過找到我喜歡工作的地方,在我的硬體上投入更多的時間和金錢,找到我「喜歡」使用的工具,設定目標,保持好奇心並專注於“為什麼”,定義專注工作的時間,專注於保持PR 較小,並寫下我能寫下的一切,我可以誠實地說,我的開發者體驗得到了極大的改善。 我非常希望這些技巧中至少一兩個也能改善您的體驗。 感謝您讓我與您分享這些技巧。下次見,駭客快樂!

JSON 效能比較慢。這邊介紹 4 個更快的替代方案

原文出處:https://dev.to/nikl/json-is-slower-here-are-its-4-faster-alternatives-2g30 --- ## 介紹 在快節奏的 Web 開發世界中,速度和反應能力是不容妥協的。您的用戶希望即時存取資訊、快速互動和無縫體驗。 JSON 是 JavaScript 物件表示法的縮寫,一直是 Web 開發中資料交換的忠實夥伴,但它會減慢您的應用程式速度嗎?讓我們深入探討 JSON 的世界,探索其潛在瓶頸,並發現更快的替代方案和優化技術,讓您的應用程式像獵豹一樣衝刺。 --- 您可能還想查看本教學:[使用 Golang 建立即時通知系統 - 逐步通知系統設計指南](https://dev.to/nikl/using-golang-to-build -即時通知系統逐步通知系統設計指南-50l7) --- ### 什麼是 JSON 以及為什麼您應該關心? 在開始 JSON 優化之旅之前,讓我們先了解 JSON 是什麼以及它為何重要。 JSON 是將應用程式中的資料黏合在一起的黏合劑。它是伺服器和客戶端之間通訊資料的語言,也是資料儲存在資料庫和設定檔中的格式。從本質上講,JSON 在現代 Web 開發中發揮關鍵作用。 了解 JSON 及其細微差別不僅是任何 Web 開發人員的基本技能,而且對於優化應用程式也至關重要。隨著我們深入研究此博客,您將發現為什麼 JSON 在性能方面可以成為一把雙刃劍,以及這些知識如何對您的開發之旅產生重大影響。 ## JSON 的受歡迎程度以及人們使用它的原因 JSON 在 Web 開發領域的受歡迎程度怎麼強調都不為過。由於以下幾個令人信服的原因,它已成為資料交換的事實上的標準: 1. **人類可讀格式**:JSON 使用簡單的、基於文字的結構,開發人員和非開發人員都可以輕鬆閱讀和理解。這種人類可讀的格式增強了協作並簡化了調試。 ``` // Inefficient { "customer_name_with_spaces": "John Doe" } // Efficient { "customerName": "John Doe" } ``` 2. **與語言無關**:JSON 不依賴任何特定的程式語言。它是一種通用資料格式,幾乎可以由所有現代程式語言解析和生成,因此具有高度通用性。 3. **資料結構一致性**:JSON 使用鍵值對、陣列和巢狀物件強制資料結構一致。這種一致性使其在各種程式設計場景中都可預測且易於使用。 ``` // Inefficient { "order": { "items": { "item1": "Product A", "item2": "Product B" } } } // Efficient { "orderItems": ["Product A", "Product B"] } ``` 4. **瀏覽器支援**:Web 瀏覽器原生支援 JSON,允許 Web 應用程式與伺服器無縫通訊。這種原生支援對其在 Web 開發中的採用做出了重大貢獻。 5. **JSON API**:許多Web服務和API預設提供JSON格式的資料。這進一步鞏固了 JSON 作為 Web 開發中資料交換首選的角色。 6. **JSON Schema**:開發人員可以使用 JSON Schema 來定義和驗證 JSON 資料的結構,為其應用程式添加額外的清晰度和可靠性。 有鑑於這些優勢,全球開發人員依賴 JSON 來滿足資料交換需求也就不足為奇了。然而,當我們更深入地探索部落格時,我們將發現與 JSON 相關的潛在性能挑戰以及如何有效解決這些挑戰。 ## 對速度的極品 在當今快節奏的數位環境中,應用程式速度和回應能力是不容談判的。用戶期望跨網路和行動應用程式即時存取資訊、快速互動以及無縫體驗。這種對速度的需求是由以下幾個因素所驅動的: ### 用戶期望 使用者已經習慣了數位互動中閃電般的快速回應。他們不想等待網頁加載或應用程式回應。即使是幾秒鐘的延遲也會導致沮喪和放棄。 ### 競爭優勢 速度可以成為顯著的競爭優勢。快速回應的應用程式往往比反應遲緩的應用程式更有效地吸引和留住用戶。 ### 搜尋引擎排名 像 Google 這樣的搜尋引擎將頁面速度視為排名因素。載入速度更快的網站往往在搜尋結果中排名更高,從而提高可見度和流量。 ### 轉換率 尤其是電子商務網站,他們敏銳地意識到速度對轉換率的影響。更快的網站可以帶來更高的轉換率,從而增加收入。 ### 移動效能 隨著行動裝置的普及,對速度的需求變得更加重要。行動用戶的頻寬和處理能力通常有限,因此需要快速的應用程式效能。 ### JSON 會減慢我們的應用程式速度嗎? 現在,讓我們解決核心問題:JSON 是否會減慢我們的應用程式速度? 如同前面提到的,JSON 是一種非常流行的資料交換格式。它靈活、易於使用且廣受支援。然而,這種廣泛的採用並不能使其免受性能挑戰。 在某些情況下,JSON 可能是降低應用程式速度的罪魁禍首。解析 JSON 資料的過程,尤其是在處理大型或複雜結構時,可能會消耗寶貴的毫秒時間。此外,低效率的序列化和反序列化可能會影響應用程式的整體效能。 ### 解析開銷 當 JSON 資料到達您的應用程式時,它必須經過解析過程才能將其轉換為可用的資料結構。解析可能相對較慢,尤其是在處理大量或深層巢狀的 JSON 資料時。 ``` // JavaScript example using JSON.parse for parsing const jsonData = '{"key": "value"}'; const parsedData = JSON.parse(jsonData); ``` ### 序列化與反序列化 JSON 要求資料從用戶端傳送到伺服器時進行序列化(將物件編碼為字串),並在接收時進行反序列化(將字串轉換回可用物件)。這些步驟可能會帶來開銷並影響應用程式的整體速度。 ``` // Node.js example using JSON.stringify for serialization const data = { key: 'value' }; const jsonString = JSON.stringify(data); ``` ### 字串操作 JSON 是基於文字的,嚴重依賴字串操作來進行連接和解析等操作。與處理二進位資料相比,字串處理可能會慢一些。 ### 缺乏資料類型 JSON 具有一組有限的資料類型(例如字串、數字、布林值)。複雜的資料結構可能需要效率較低的表示,導致記憶體使用量增加和處理速度變慢。 ``` { "quantity": 1.0 } ``` ### 冗長 JSON 的人類可讀設計可能會導致冗長。冗餘金鑰和重複結構會增加有效負載大小,導致資料傳輸時間更長。 ``` // Inefficient { "product1": { "name": "Product A", "price": 10 }, "product2": { "name": "Product A", "price": 10 } } ``` ### 沒有二進位支持 JSON 缺乏對二進位資料的本機支援。在處理二進位資料時,開發人員通常需要將其編碼和解碼為文本,這可能會降低效率。 ### 深度嵌套 在某些場景下,JSON資料可能會深度嵌套,需要遞歸解析和遍歷。這種計算複雜性可能會減慢您的應用程式的速度,尤其是在沒有最佳化的情況下。 --- > **與此類似,我與其他熱愛開源的開發人員一起在 Slack 上運行一個以開發人員為中心的社群。我們討論這些類型的主題、實現、整合、一些真相炸彈、奇怪的聊天、虛擬會議、為開源做出貢獻以及一切有助於開發人員保持理智的事情;)畢竟,太多的知識也可能是危險的。* * > **我邀請您加入我們的免費社區(_沒有廣告,我保證,並且我打算保持這種方式_),參與討論,並分享您的經驗和專業知識。您可以填寫此表格,Slack 邀請將在幾天後收到您的電子郵件。我們有來自一些偉大公司(Atlassian、Gong、Scaler)的優秀人員,您一定不想錯過與他們的互動。 [邀請表](https://forms.gle/VzA3ST8tCFrxt39U9)** 讓我們繼續... --- ## JSON 的替代方案 雖然 JSON 是一種通用的資料交換格式,但其在某些場景下的效能限制導致人們探索更快的替代方案。讓我們深入研究其中一些替代方案,並了解您何時以及為何選擇它們: ### 協定緩衝區 Protocol Buffers,也稱為 protobuf,是 Google 開發的二元序列化格式。它在速度和效率方面表現出色。這就是您可能考慮使用 Protocol Buffer 的原因: 1. **二進位編碼**:Protocol Buffers 使用二進位編碼,與 JSON 基於文字的編碼相比,它更緊湊,編碼和解碼速度更快。 2. **高效的資料結構**:Protocol Buffers 可讓您透過精確的類型定義高效的資料結構,從而實現更快的序列化和反序列化。 3. **架構演化**:Protocol Buffers 支援架構演化,這表示您可以在不破壞向後相容性的情況下更新資料結構。 ``` syntax = "proto3"; message Person { string name = 1; int32 age = 2; } ``` ### 訊息包 MessagePack 是另一種專為提高效率和速度而設計的二元序列化格式。以下是您可能考慮使用 MessagePack 的原因: 1. **緊湊性**:MessagePack 產生高度緊湊的資料表示形式,從而減少資料傳輸大小。 2. **二進位資料**:MessagePack 提供了對二進位資料的原生支持,非常適合涉及二進位資訊的場景。 3. **速度**:MessagePack 的二進位性質允許快速編碼和解碼。 ``` // JavaScript example using MessagePack for serialization const msgpack = require('msgpack-lite'); const data = { key: 'value' }; const packedData = msgpack.encode(data); ``` ### BSON(二進位 JSON) BSON,通常發音為“bee-son”或“bi-son”,是一種二進位序列化格式,主要用於 MongoDB 等資料庫。以下是您可能考慮使用 BSON 的原因: 1. **類似 JSON 的結構**:BSON 維護了類似 JSON 的結構,並添加了二進位資料類型,在效率和可讀性之間提供了平衡。 2. **二進位資料支援**:BSON 提供對二進位資料類型的原生支持,這有利於處理映像或多媒體等資料。 3. **資料庫集成**:BSON 與 MongoDB 等資料庫無縫集成,使其成為此類環境的自然選擇。 ``` { "_id": ObjectId("60c06fe9479e1a1280e6bfa7"), "name": "John Doe", "age": 30 } ``` ### 歐元 Avro 是在 Apache Hadoop 專案中開發的資料序列化框架。它強調模式相容性和效能。以下是您可能考慮使用 Avro 的原因: 1. **架構相容性**:Avro 優先考慮架構相容性,讓您在不破壞相容性的情況下發展資料結構。 2. **二進位資料**:Avro 使用緊湊的二進位編碼格式進行資料傳輸,從而產生更小的有效負載。 3. **語言中立**:Avro 支援多種程式語言,使其適合不同的應用程式生態系統。 ``` { "type": "record", "name": "Person", "fields": [ { "name": "name", "type": "string" }, { "name": "age", "type": "int" } ] } ``` JSON 及其替代方案之間的選擇取決於您的特定用例和要求。如果架構相容性至關重要,Avro 可能是最佳選擇。如果您需要緊湊性和效率,MessagePack 和 Protocol Buffers 是強有力的競爭者。在處理二進位資料時,MessagePack 和 BSON 可以滿足您的需求。每種格式都有其優點和缺點,因此請選擇適合您專案需求的格式。 ### 最佳化 JSON 效能 但是,如果您決心使用 JSON,儘管它有潛在的速度障礙,該怎麼辦?如何讓 JSON 運作得更快、更有效率?好消息是,有一些實用的策略和最佳化可以幫助您實現這一目標。讓我們透過程式碼範例和最佳實踐來探索這些策略。 **1.最小化資料大小** A。 **使用簡短的描述性鍵**:選擇簡潔但有意義的鍵名稱以減少 JSON 物件的大小。 ``` // Inefficient { "customer_name_with_spaces": "John Doe" } // Efficient { "customerName": "John Doe" } ``` b. **盡可能縮寫**:在不犧牲清晰度的情況下,考慮使用鍵或值的縮寫。 ``` // Inefficient { "transaction_type": "purchase" } // Efficient { "txnType": "purchase" } ``` **2.明智地使用數組** A。 **最小化巢狀**:避免深度巢狀數組,因為它們會增加解析和遍歷 JSON 的複雜度。 ``` // Inefficient { "order": { "items": { "item1": "Product A", "item2": "Product B" } } } // Efficient { "orderItems": ["Product A", "Product B"] } ``` **3.優化數位表示** A。 **盡可能使用整數**:如果一個值可以表示為整數,請使用它而不是浮點數。 ``` // Inefficient { "quantity": 1.0 } // Efficient { "quantity": 1 } ``` **4.刪除冗餘** A。 **避免重複資料**:透過引用共享值來消除冗餘資料。 ``` // Inefficient { "product1": { "name": "Product A", "price": 10 }, "product2": { "name": "Product A", "price": 10 } } // Efficient { "products": [ { "name": "Product A", "price": 10 }, { "name": "Product B", "price": 15 } ] } ``` **5.使用壓縮** A。 **套用壓縮演算法**:如果適用,請使用 Gzip 或 Brotli 等壓縮演算法來減少傳輸過程中 JSON 有效負載的大小。 ``` // Node.js example using zlib for Gzip compression const zlib = require('zlib'); const jsonData = { // Your JSON data here }; zlib.gzip(JSON.stringify(jsonData), (err, compressedData) => { if (!err) { // Send compressedData over the network } }); ``` 根據塞繆爾的評論,我添加了一個編輯。 {% devcomment 2adn4 %} ``` As Samuel rightly observes, the adoption of HTTP/2 has brought significant advancements, particularly in optimizing data interchange formats like JSON. HTTP/2's multiplexing capabilities efficiently manage multiple requests over a single connection, enhancing responsiveness and reducing overhead. In practical terms, a comprehensive optimization strategy may involve both embracing HTTP/2 and utilizing compression techniques per your use-case, recognizing that each approach addresses specific aspects of network efficiency and performance. HTTP/2 excels in network-level optimization, while compression strategies enhance application-level efficiency, and the synergy between them can lead to substantial gains in data handling speed and resource utilization. ``` **6。使用伺服器端快取** A。 **快取 JSON 回應**:實作伺服器端快取以有效儲存和提供 JSON 回應,減少重複資料處理的需要。 **7.設定檔和優化** A。 **分析效能**:使用分析工具來識別 JSON 處理程式碼中的瓶頸,然後最佳化這些部分。 請記住,您實施的具體優化應符合應用程式的要求和約束。 ### 實際最佳化:在實作中加速 JSON 現在您已經探索了優化 JSON 的理論方面,現在是時候深入研究遇到 JSON 效能瓶頸並巧妙克服它們的實際應用程式和專案了。這些範例提供了有關用於提高速度和回應能力的策略的寶貴見解,同時仍利用 JSON 的多功能性。 **1. LinkedIn 的協定緩衝區整合** *挑戰:LinkedIn 與 JSON 冗長和網路頻寬使用的鬥爭* 全球最大的職業社交平台LinkedIn面臨嚴峻的挑戰。他們對 JSON 進行微服務通訊的依賴導致了冗長和網路頻寬使用量的增加,最終導致更高的延遲。在每一毫秒都至關重要的數位世界中,這是一個需要解決方案的挑戰。 **解決方案:協定緩衝區的力量** LinkedIn 轉向了 [Protocol Buffers](https://engineering.linkedin.com/blog/2023/linkedin-integrates-protocol-buffers-with-rest-li-for-improved-m),通常稱為 protobuf,由Google開發的二進位序列化格式。 Protocol Buffers 的主要優勢在於其效率、緊湊性和速度,使其在序列化和反序列化方面比 JSON 快得多。 **影響:延遲減少高達 60%** Protocol Buffers 的採用顯著降低了延遲,報告顯示延遲可提高高達 60%。此次優化顯著提高了 LinkedIn 服務的速度和回應能力,為全球數百萬用戶提供了更流暢的體驗。 **2. Uber 的 H3 地理索引** *挑戰:Uber 在地理空間資料方面的 JSON 困境* 叫車巨頭優步的營運嚴重依賴地理空間數據。 JSON 是表示地理空間資料的預設選擇,但解析大型資料集的 JSON 被證明是一個瓶頸,減慢了演算法的速度。 **解決方案:引入 H3 地理索引** Uber 推出了 [H3 Geo-Index](https://www.uber.com/en-IN/blog/h3/),這是一種用於地理空間資料的高效能六邊形網格系統。透過從 JSON 轉向這種創新解決方案,他們成功地大幅減少了 JSON 解析開銷。 **影響:加速地理空間操作** 這種優化大大加速了地理空間操作,提高了 Uber 乘車服務和地圖系統的效率。使用者體驗到更快的回應時間和更可靠的服務。 **3. Slack 的訊息格式最佳化** *挑戰:Slack 與即時訊息渲染的戰鬥* Slack 是團隊的訊息平台,需要在即時聊天中傳輸和呈現大量 JSON 格式的訊息。然而,這導致了效能瓶頸和訊息渲染緩慢。 **解決方案:簡化 JSON 結構** Slack 優化了 JSON 結構以減少不必要的資料。他們開始在每個訊息中只包含基本訊息,從而減少有效負載的大小。 **影響:更快的訊息渲染和增強的聊天效能** 此優化顯著提高了訊息渲染速度。 Slack 用戶享受到更靈敏、更有效率的聊天體驗,尤其是在繁忙的群組聊天中。 **4. Auth0 的協定緩衝區實作** *挑戰:Auth0的身份驗證和授權資料效能* Auth0 是一個著名的身份和存取管理平台,在處理身份驗證和授權資料時面臨 JSON 的效能挑戰。這些數據需要在不影響安全性的情況下有效處理。 **解決方案:採用協定緩衝區進行資料序列化** [Auth0 也轉向Protocol Buffers](https://auth0.com/blog/beating-json-performance-with-protobuf/#How-Do-We-Use-Protobuf),利用其高效的資料序列化和反序列化功能。此交換器顯著提高了資料處理速度,使身份驗證過程更快並增強了整體效能。 **影響:加速身份驗證和授權** Protocol Buffers 的採用增強了身分驗證和授權流程,確保 Auth0 的服務提供一流的效能,同時保持最高的安全標準。 這些現實世界的例子強調了優化在克服 JSON 相關的速度下降方面的力量。這些案例中採用的策略證明了 JSON 和替代格式在滿足現代數位環境的需求方面的適應性和多功能性。 請繼續關注結論部分,我們總結了關鍵要點,並為您提供了在您自己的專案中優化 JSON 效能的路線圖。 ## 結束語 在開發領域,JSON 是一種多功能且不可或缺的資料交換工具。其人類可讀的結構和跨語言適應性使其成為當代應用程式的基石。然而,正如我們在本指南中的探索所揭示的那樣,JSON 的普遍使用並不能使其免受性能挑戰。 我們在增強 JSON 效能的過程中獲得的重要收穫是顯而易見的: - 1. **效能至關重要:** 速度和反應能力在當今的數位環境中至關重要。用戶要求應用程式以閃電般的速度運行,即使是輕微的延遲也會導致不滿意並錯失機會。 - 2. **大小很重要:** 資料有效負載的大小直接影響網路頻寬使用和回應時間。減少資料大小通常是優化 JSON 效能的第一步。 - 3. **探索替代格式:** 當效率和速度至關重要時,探索替代資料序列化格式(如 Protocol Buffers、MessagePack、BSON 或 Avro)是有益的。 - 4. **真實世界範例:** 從組織有效解決 JSON 相關減速問題的真實實例中學習,表明最佳化工作可以顯著提高應用程式效能。 當您繼續開發和增強 Web 應用程式時,請務必牢記 JSON 對效能的影響。精心設計資料結構,選擇有意義的鍵名稱,並在情況需要時開放探索替代序列化格式。透過這樣做,您可以確保您的應用程式在速度和效率方面不僅滿足而且超越用戶的期望。 在不斷發展的 Web 開發環境中,優化 JSON 效能成為一項寶貴的資產,使您的專案與眾不同,並確保您的應用程式在即時數位體驗時代蓬勃發展。 --- > **與此類似,我與其他熱愛開源的開發人員一起在 Slack 上運行一個以開發人員為中心的社群。我們討論這些類型的主題、實現、整合、一些真相炸彈、奇怪的聊天、虛擬會議、為開源做出貢獻以及一切有助於開發人員保持理智的事情;)畢竟,太多的知識也可能是危險的。* * > **我邀請您加入我們的免費社區(_沒有廣告,我保證,並且我打算保持這種方式_),參與討論,並分享您的經驗和專業知識。您可以填寫此表格,Slack 邀請將在幾天後收到您的電子郵件。我們有來自一些偉大公司(Atlassian、Gong、Scaler)的優秀人員,您一定不想錯過與他們的互動。 [邀請表](https://forms.gle/VzA3ST8tCFrxt39U9)** > _如果您能與您的開發朋友(他們是奉獻者)分享該表格,我將不勝感激。_

您的下一個專案,可以試試看 HTMX 技術!

原文出處:https://dev.to/turculaurentiu91/why-you-should-choose-htmx-for-your-next-project-o7j 在本文中,我們將旨在了解為什麼您下次為 Web 應用程式選擇技術堆疊時應該考慮 HTMX 作為 React 的替代品。我們將研究傳統 HTTP JSON API + React 帶來的複雜性和挑戰,以及如何透過使用 HTMX 輕鬆避免它們。 **注意**:在本文中,我將討論 React,但它可以替換為任何其他前端框架,如 Angular、Vue、Svelte 或 Solid,但我談論 React 是因為它是大多數 Web 的預設技術開發人員預設為。 ### HTMX 到底是什麼 如果您還不知道,[HTMX](https://htmx.org/) 是一個小型瀏覽器 (JS) 庫,它使用一些屬性擴展了 HTML,允許您使用來自伺服器。它還使 HTML 能夠對所有動詞發出 HTTP 請求,而不僅僅是 GET 和 POST。 ## React 解決了什麼問題 React 是一個 JavaScript 程式庫,可協助您透過保持使用者介面與狀態同步來編寫高度互動的應用程式。您告訴它如何渲染給定的狀態,每次更新狀態時,它都會重新渲染(盡可能有效率)UI 以反映狀態變更。 每次狀態發生變化時,您都會通知庫它發生了變化,並提供新的狀態,它將處理 UI 更新。 需要本地記憶體狀態的高互動應用程式的範例可以是您可以在網路上找到的各種文字編輯器(VSCode)之一、拖放看板(如 Trello 或 JIRA)、視訊播放器或聊天室。 什麼不是此類應用程式的範例?您正在建立的待辦事項清單、您正在閱讀的新聞網站、您發布的部落格以及周圍的大多數網站。如果我們看一下 [80/20 規則](https://en.wikipedia.org/wiki/Pareto_principle) >80% 的結果來自 20% 的原因,80% 的結果來自 20% 的努力。 你可以說 80% 使用 React 的 Web 應用程式不需要本地狀態。對於那 20% 需要它的人來說,你可以說它只是應用程式的一小部分(大約 20%),其餘部分只能用 HTML 來表達。 **這些數字是編造的,我沒有任何研究來支持這一點** ## React 也解決了哪些問題,使其被現代網站廣泛採用 HTML 已經過時了。使用 HTML 製作應用程式的舊方法涉及頁面、連結和表單的集合,這些頁面、連結和表單向使用者描述給定資源的當前狀態以及他們可以執行哪些操作來更改它。 每次使用者與資源互動時,應用程式只能重新載入整個頁面以顯示資源的新狀態。 幾年後,FaceBook 推出了 React,這是一個 JS 程式庫,可讓開發人員建立單頁應用程式 (SPA)。導航時不再需要重新加載整個頁面,狀態更新的酷過渡、對用戶的有趣反饋以及其他使 Web 開發人員在其網站中採用 SPA 框架的細節。 ## 複雜性問題 ![AI 產生圖像,透過 next.js 展示現代應用程式的瘋狂複雜性](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpk0v0nz0d45hv91i166.png) 如果你不理解上面的架構,不用擔心,沒有什麼好理解的。我要求 ChantGPT 為我生成它,由於它過於複雜且沒有任何意義,它完美地反映了現代 Web 應用程式目前的預設基礎架構。 一個很酷的程式設計原則是**KISS**,它代表“保持簡單,愚蠢”,或者有些人可能喜歡開玩笑,“保持簡單,愚蠢!” 現代開發人員預設建立 Web 應用程式的當前基礎設施和技術堆疊極其複雜,做了很多不必要的事情,只是因為它很酷! 當您自己建立第一個 POC 時,效果很好,但下一刻您加入了更多團隊成員,並且使用多次迭代和「擁抱」更改的敏捷方式,它有點崩潰,原因是我們將往下看一下。 ## 傳統 HTTP JSON API + React 的狀態管理問題 在 Web 應用程式中,您經常要做的就是從資料庫中獲取資源的狀態並將其呈現給使用者。讓我們以任務管理應用程式為例。使用者有一個任務列表,每個任務都有一個狀態: - 任務的標題 - 說明 - 任務完成時的標誌 - 截止日期(可選) 我們通常將此狀態儲存在資料庫中,並將此資訊呈現給用戶,您必須: - 從使用者有權存取的資料庫中取得所有任務。 - 可以選擇轉換資料(也許您儲存完成的日期並從中計算「is_completed」標誌)。 - 將資料序列化為 JSON。 - 透過 HTTP 請求取得資料。 - (可選但通常)根據模式驗證資料,可能使用 YUP 或 ZOD。 - 使用 Redux、Zustand、react-query 或其他狀態管理庫將 JSON 轉換為狀態並將其儲存在快取中。 - 轉換 HTML 中的狀態,通常會決定使用者可以使用資料做什麼。 簡而言之,我們正在描述如何在 JavaScript 中渲染所有資源的所有可能狀態,在瀏覽器中下載所述 JavaScript,然後 JavaScript 下載一堆 JSON 格式的資料並將其渲染(如果它知道如何)瀏覽器顯示為HTML! 向使用者顯示任務清單需要做大量工作,特別是當任務僅在使用者變更時才更改時,應用程式必須將應用程式置於載入狀態,發出另一個 HTTP 請求(以PUT 或PATCH 或DELETE)使快取值(狀態)無效並重新獲取它以顯示更改的任務。 或者更糟的是,當用戶更改某些任務時,樂觀地更新本地狀態並立即顯示更改,並在幕後執行更新請求,僅在他們成功更新後通知用戶更新失敗。 這是非常容易出錯的。對於這個待辦事項應用程式來說,它可能會很有效,因為您是唯一的開發人員,並且該應用程式足夠大,您可以在腦海中保留正在發生的所有事情的地圖。但是當你的團隊規模更大時,尤其是當你將團隊分成前端和後端時,溝通不良可能會導致很多問題。 後端可能使用“is_completed”標誌,而前端可能需要“is_active”標誌。後端可能會將經過 Markdown 處理的「描述」傳送到 HTML,而前端可能希望它不被處理。後端可能會將“描述”設為可選,以允許使用者在前端不同步時保存草稿,並且您會看到許多“未捕獲的類型錯誤:無法讀取未定義的屬性(讀取“toLowerCase” )” 另一方面,在 HTMX 上,您可以直接在範本上呈現 HTML,只要後端語言允許,就可以確保類型安全。您僅向瀏覽器發送相關訊息,向使用者提供對資源的適當控制,並指示瀏覽器或 HTMX 如何解釋使用者操作以及後端對這些操作的回應。所有應用程式狀態都是 HTML,這個概念稱為 [HATEOAS](https://htmx.org/essays/hateoas/) ## 傳統 HTTP JSON API + React 對文件的需求 為了讓兩個團隊(後端和前端)獨立工作並透過 HTTP JSON API 進行通信,您需要擁有適當的 API 文件。您還需要記錄如何計算使用者可以對給定資源執行哪些操作以顯示控制項。 大多數此類文件寫起來都很痛苦,特別是因為通常需要在實作之前編寫,而開發人員尚未完全理解問題的範圍,因此前端可以並行開發。這通常會在開發過程中進行許多更新,以適應開發過程中出現的問題,並可能導致團隊之間的版本不一致。 您還需要對 API 進行版本控制,並注意不要在非主要版本變更中引入重大變更。您無法再在不變更主要版本的情況下變更欄位名稱。您還需要保持 API 的多個版本運作或強制前端團隊進行調整。 大多數時候,文件已經過時了。有些必須緊急修復,有些新要求是在發布前一天提出的,現在您的文件已經過時,即使在很短的時間內。而且您必須記住更新它,或者更糟的是,您建立了一張票來記住它,而其他人拿起它,但沒有完整的圖片並記錄了錯誤! ## 重複的邏輯問題 對於每個資源,您必須實施授權策略。您必須確定目前使用者是否可以將任務 **46234** 標記為已完成。您必須在後端程式碼的某個位置編寫此檢查。否則,您的應用程式將開放給_不安全的直接物件參考_,或任何使用 Postman 的人都可以將您的任務標記為已完成。 您還必須在前端實現相同的邏輯,僅當用戶有權標記已完成時才顯示標記按鈕(讓我們假設您可以與其他用戶共享您的任務,但只有您可以更改它們)。 現在每次這個邏輯改變時,你都必須在兩個應用程式中實作它,並同時發布它或擁有多個版本的API。 ## 效能問題 為了使用 React 在瀏覽器中呈現網站,您需要將佔用大量內存和解析/處理影響的 React 程式碼、狀態管理庫程式碼、腳趾 UI 庫程式碼、CSS-IN- 捆綁在一起。JS 庫程式碼、應用程式程式碼以及我們透過NPM 安裝和使用的任何js 程式庫(我們並不羞於安裝新套件,請參閱[leftpad 問題](https://www.theregister.com/2016/03/23/npm_left_pad_chaos/))。這通常會導致透過網路傳送大塊的 JavaScript 資源。當然,您可以在瀏覽器中緩存,但在現代敏捷開發中,每個衝刺至少部署一次,因此這無法解決任何問題。這會消耗網路流量和電池,這對行動裝置來說是一個經常被忽視的問題。 上述JavaScript需要由瀏覽器來解釋,消耗處理能力和電池。 JavaScript,尤其是 ReactDOM,需要追蹤 DOM 的鏡像。在其之上加入普通 DOM 和本地狀態緩存,以及所有渲染函數,以及所有“useMemo”、“useCallback”和“useState”。也要加入需要在記憶體中保存所有上下文變數的所有閉包。 JavaScript 引擎並不以其記憶體效率而聞名!您會聽到人們抱怨瀏覽器消耗了多少內存,但他們低估了他們存取的網站所消耗的內存量。 所有這些加起來,最終會耗盡用戶的電池和記憶體。當然,您可以付出努力並優化所有這些,或使用其他程式庫(如 Svelte),但所有這些努力都可以用於為您的用戶提供更有意義的功能。 ## 服務端渲染的需要 近年來,我們播種了伺服器端渲染專用框架(如「Next.js」)的興起。它們的流行凸顯了對以 HTML 格式交付內容的需求,特別是出於可存取性優化、效能和搜尋引擎優化的原因。 你不想等待瀏覽器下載 JavaScript 來渲染頁面,然後等待 JavaScript 發出 HTTP 請求來獲取內容然後渲染它,你希望它立即渲染,特別是對於上面的情況折疊內容。 這又增加了一層複雜性,包括: - 基礎設施,現在您還需要另一台伺服器用於前端應用程式 - 程式碼更複雜,包括什麼程式碼在伺服器上執行以及什麼在瀏覽器上執行的思維導圖 - 部署管道現在更加複雜 - 測試基礎設施現在更加複雜 - 現在解決問題變得更加困難,您需要了解問題是在瀏覽器上、在客戶端應用程式伺服器上還是在 API 伺服器上 ## 解決這些問題 Web 開發社群各自使用自己的語言或開發技術,以不同的方式解決這些問題: - Next.js(以及 Nuxt 等) - 反應伺服器元件 - 拉維爾 - 慣性.JS - 活線 - 點網 - Blazor 頁面 - 靈藥 - 鳳凰即時查看 - 鐵鏽 - 樂浦伺服器功能 還有許多我忘記或從未聽說過的其他解決方案! 無論如何,此類解決方案的存在和流行證明了這些問題是有效的並且在 Web 開發人員的日常生活中遇到過。否則他們不會不遺餘力地解決這些問題,尤其是以開源的方式! 還有 [Turbo](https://turbo.hotwired.dev/) 以及採用它們的框架、Ruby on Rails、PHP Symphony 以及其他可能以與 HTMX 相同的方式解決相同問題的框架。選擇 HTMX 只是個人喜好,但你絕對應該了解這一點,這和 HTMX 一樣酷! 在所有這些中,HTMX 脫穎而出,不僅因為它不會將您鎖定到特定技術,您可以透過對模板進行微小更改來從 PHP 切換到 Rust,而且還因為它完全消除了對有狀態元件的需求,或者需要追蹤與資源無關的應用程式的某種狀態。 例如,讓我們採用確認對話方塊模式。您通常最終要做的是,您有一個本地記憶體狀態(如果它是開啟的),並根據該狀態將其顯示給使用者。在 HTMX 中,狀態 **IS THE HTML** 意味著當您按一下開啟模式時,您 **GET** `tasks/{taskId}/confirm-delete` 並將回應 HTML 嵌入到 DOM 中。當它被刪除時,您就完全刪除了模式和任務!這以一種獨特且極其簡單的方式解決了上述所有問題,您不需要: - 追蹤狀態 - 知道如何渲染對話框 - 記錄API - 檢查使用者是否可以刪除任務(在前端) - 您的後端應用程式始終負責 - 您可以獲得更好的安全性,因為您不會向瀏覽器發送不相關的資料並竊取敏感資訊 - 你會得到更好的表現 __*最重要的是,您可以讓您的應用程式保持簡單,只有在解決使用者問題時才允許複雜性!*__ 您只需指示 HTMX 從何處獲取對話框以及將其放置在何處,一切就完成了! ``` <!-- the delete button --> @if ($chirp->user->is(auth()->user())) <form> @csrf @method('delete') <x-dropdown-link :component="'button'" type="submit" hx-get="{{ route('chirps.confirm-destroy', $chirp) }}" hx-swap="beforeend" hx-target="closest .chirp" > {{ __('Delete') }} </x-dropdown-link> </form> @endif <!-- the dialog template --> <div class="modal fixed z-10 inset-0 overflow-y-auto flex justify-center items-center bg-black bg-opacity-50" style="backdrop-filter: blur(14px);"> <div class="bg-white rounded p-6"> <h2 class="text-xl border-b pb-2 mb-2">Confirm Action</h2> <p>Are you sure you want to delete this chirp?</p> <div class="flex justify-end mt-4 gap-4"> <x-secondary-button _="on click remove closest .modal" > Cancel </x-secondary-button> <form> @csrf <x-danger-button hx-delete="{{route('chirps.destroy', $chirp)}}" hx-target="closest .chirp" hx-swap="delete"> Delete </x-danger-button> </form> </div> </div> </div> ``` _這個範例來自我的 [HTMX with Laravel] 教學(https://dev.to/turculaurentiu91/laravel-htmx--g0n) ,請看!_ 就像這樣,當我們單擊刪除按鈕時,我們指示 HTMX 對 `chirps/{chirp}/confirm-destroy` 執行 **GET** 請求,並將結果 HTML 放在最接近的父級 `< 之前div class="chirp">` 結束(在底部)。在刪除對話方塊中,當使用者確認時,我們指示 HTMX 對 `chirps/{chirp}` 端點執行 **DELETE** 請求,成功後,我們刪除具有 `chirp` 類別的最接近的父級。 ## 結論 在不斷發展的 Web 開發領域,看到 HTMX 等倡導簡單性和回歸基礎的工具令人耳目一新。透過利用 HTML 和 HTTP 的強大功能,HTMX 允許開發人員建立動態 Web 應用程式,而無需傳統 JavaScript 框架的複雜性和開銷。 因此,下次您開始新專案或考慮重構現有專案時,請嘗試 HTMX。您可能會驚訝於用這麼少的錢就能取得如此大的成就。

從開源專案複製程式碼,算是偷竊嗎?

原文出處:https://dev.to/rjwignar/are-you-stealing-from-other-projects-4f0n ### 優秀的程式設計師抄襲;更優秀的程式設計師會偷竊嗎? > 優秀的藝術家臨摹;偉大的藝術家偷竊 > \- 巴勃羅·畢卡索 巴勃羅·畢卡索經常因說出上述引言而受到讚譽。我不會提供我自己對這句話的解釋,因為有興趣的人可以在網路上找到大量的解釋。然而,我經常想知道這句話如何應用於程式設計和開源開發。 ### 我們的程式設計知識真的是我們自己的嗎? 網路上有很多資源可以教您幾乎所有與程式設計相關的知識。我自己和我的許多同學幾乎所有的程式設計知識都是從教科書、線上資源/文件和 YouTube 教學中學到的。這些資源教會了我們今天擁有並應用的程式設計知識,但是所有這些知識都被盜了嗎?如果您從數千人觀看的 YouTube 教程中學會了編寫第一個“Hello World”程序,您是否在竊取程式碼?影片上傳者也學會使用外部資源編寫「Hello World」程式的可能性有多大?上傳者的知識被偷了嗎? 我不能說這些知識是否被盜,但我相信這種知識對於我們建立和擴展我們的技能是必要的。沒有人能夠知道每一個完美的實現,或是為每個專案編寫每一行程式碼。 ### 開源軟體是為使用而生的! 開源軟體的存在不應該被忽視,開源軟體是用來使用的!如果開源專案提供了您需要的功能,那麼當其他人為您精心完成這些功能時,為什麼還要自己實現這些功能呢?只要該庫得到積極維護並滿足您的需要,您就應該使用和研究開源庫。如果您發現任何錯誤、問題或缺少的功能,請嘗試建立自己的解決方案並為該專案做出貢獻,使其對每個人都更好!同樣,如果開源程式庫提供了您需要的功能,但您無法整合該程式庫,請以開源專案為靈感,在您自己的專案中實現該功能。 ### 從其他專案中獲取靈感 在我之前的[關於程式碼閱讀的部落格文章](https://dev.to/rjwignar/code-reading-i7h)中,我閱讀了Docusaurus 的程式碼庫來研究該專案如何為受保護的程式碼區塊實現語法突出顯示。我的研究告訴我,Docusaurus 實際上使用第三方函式庫 [Prism-React-Renderer](https://github.com/FormidableLabs/prism-react-renderer) 來提供語法突出顯示。這些知識很有用,因為我想在我的 Markdown 到 HTML 轉換器 [ctil](https://github.com/rjwignar/ctil) 新增語法突出顯示,但不想從頭開始實現該功能。雖然我無法在自己的專案中使用 Prism React Renderer,但研究 Docusaurus 給了我找到一個我可以使用的開源程式庫的想法。 ### 使用highlight.js 進行語法高亮顯示 在搜尋第三方語法螢光筆時,我找到了 [highlight.js](https://highlightjs.org/)。 ctil 將文字(`.txt`)和 Markdown (`.md`)轉換為生成的 HTML(`.html`)文件,因此我希望生成的 HTML 文件支援語法突出顯示。透過使用內容分發網路(CDN),highlight.js 可以用作 HTML 標籤,因此我可以透過將以下行新增至產生的 HTML 檔案來新增highlight.js: ``` <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> <script>hljs.highlightAll();</script> ``` 透過這些行,我能夠[加入語法突出顯示](https://github.com/rjwignar/ctil/commit/c597f05724b60731e129aead33f1c3017a99ec17): ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k74q82mbi46fbbqupbri.png) ### 與 Docusaurus 方法的比較 在我的[上一篇部落格文章](https://dev.to/rjwignar/code-reading-i7h)中,我描述了 Docusaurus 如何使用 Prism React Renderer 新增語法突出顯示。與 Docusaurus 一樣,我使用了第三方函式庫highlight.js 來加入語法突出顯示。然而,在 Docusaurus 中新增和修改了設定檔以設定 Prism React Renderer,並透過使用「<Highlight />」元件加入了語法突出顯示。 Prism React Renderer 還提供了使用者可以在其專案中配置的突出顯示主題。對於我的專案,只需將highlight.js 加入到生成的 HTML 文件中,作為要透過 CDN 傳送的 HTML 標籤。現在我對基本的語法突出顯示感到滿意,所以我不關心有一個特定的突出顯示主題。透過 CDN 使用highlight.js 的一個缺點是,如果使用者在線,語法突出顯示可能無法運作。將來我可能會將highlight.js加入到專案本身中,這樣語法突出顯示就可以離線工作。 ### 下一步。 此功能仍在開發中。在目前迭代中,html 用作 `<code>...</code>` 區塊的預設語言類別。目前這是可以接受的,但此解決方案將忽略原始 Markdown 文件中的任何語言類設定。我希望 ctil 解析 Markdown 文件中的語言標籤,以確定使用哪種語言進行突出顯示。這將是未來需要解決的問題。有興趣的人可以在[此處](https://github.com/rjwignar/ctil/issues/11)找到問題。 ### 那麼從開源專案複製程式碼算不算偷竊? 只要專案許可證允許複製,並且您遵循專案許可證的要求,您就不會竊取。同樣,我認為從開源專案中尋找靈感並不是偷竊。

44 個 React 前端面試問題

原文出處:https://dev.to/m_midas/44-react-frontend-interview-questions-2o63 ## 介紹 在面試 React 前端開發人員職位時,為技術問題做好充分準備至關重要。 React 已經成為建立使用者介面最受歡迎的 JavaScript 程式庫之一,雇主通常專注於評估候選人對 React 核心概念、最佳實踐和相關技術的理解。在本文中,我們將探討 React 前端開發人員面試期間常見問題的完整清單。透過熟悉這些問題及其答案,您可以增加成功的機會並展示您在 React 開發方面的熟練程度。因此,讓我們深入探討您應該準備好在 React 前端開發人員面試中解決的關鍵主題。 ![](https://media.giphy.com/media/AYECTMLNS4o67dCoeY/giphy.gif) ### 1.你知道哪些React hooks? - `useState`:用於管理功能元件中的狀態。 - `useEffect`:用於在功能元件中執行副作用,例如取得資料或訂閱事件。 - `useContext`:用於存取功能元件內的 React 上下文的值。 - `useRef`:用於建立對跨渲染持續存在的元素或值的可變引用。 - `useCallback`:用於記憶函數以防止不必要的重新渲染。 - `useMemo`:用於記憶值,透過快取昂貴的計算來提高效能。 - `useReducer`:用於使用reducer函數管理狀態,類似於Redux的工作方式。 - `useLayoutEffect`:與 useEffect 類似,但效果在所有 DOM 變更後同步運作。 這些鉤子提供了強大的工具來管理狀態、處理副作用以及重複使用 React 功能元件中的邏輯。 [了解更多](https://react.dev/reference/react) ### 2.什麼是虛擬 DOM? 虛擬 DOM 是 React 中的一個概念,其中建立實際 DOM(文件物件模型)的輕量級虛擬表示並將其儲存在記憶體中。它是一種用於優化 Web 應用程式效能的程式設計技術。 當 React 元件的資料或狀態發生變更時,虛擬 DOM 會被更新,而不是直接操作真實 DOM。然後,虛擬 DOM 計算元件的先前狀態和更新狀態之間的差異,稱為「比較」過程。 一旦辨識出差異,React 就會有效地僅更新真實 DOM 的必要部分以反映變更。這種方法最大限度地減少了實際 DOM 操作的數量,並提高了應用程式的整體效能。 透過使用虛擬 DOM,React 提供了一種建立動態和互動式使用者介面的方法,同時確保最佳效率和渲染速度。 ### 3. 如何渲染元素陣列? 要渲染元素陣列,可以使用“map()”方法迭代該陣列並傳回一個新的 React 元素陣列。 ``` const languages = [ "JavaScript", "TypeScript", "Python", ]; function App() { return ( <div> <ul>{languages.map((language) => <li>{language}</li>)}</ul> </div> ); } ``` [了解更多](https://react.dev/learn/rendering-lists) ### 4. 受控元件和非受控元件有什麼不同? 受控元件和非受控元件之間的區別在於**它們如何管理和更新其狀態**。 受控元件是狀態由 React 控制的元件。元件接收其當前值並透過 props 更新它。當值改變時它也會觸發回調函數。這意味著該元件不儲存其自己的內部狀態。相反,父元件管理該值並將其傳遞給受控元件。 ``` import { useState } from 'react'; function App() { const [value, setValue] = useState(''); return ( <div> <h3>Controlled Component</h3> <input name="name" value={name} onChange={(e) => setValue(e.target.value)} /> <button onClick={() => console.log(value)}>Get Value</button> </div> ); } ``` 另一方面,不受控制的元件使用 refs 或其他方法在內部管理自己的狀態。它們獨立儲存和更新狀態,不依賴 props 或回呼。父元件對不受控元件的狀態控制較少。 ``` import { useRef } from 'react'; function App() { const inputRef = useRef(null); return ( <div className="App"> <h3>Uncontrolled Component</h3> <input type="text" name="name" ref={inputRef} /> <button onClick={() => console.log(inputRef.current.value)}>Get Value</button> </div> ); } ``` [了解更多](https://react.dev/learn/sharing-state- Between-components#driven-and-uncontrol-components) ### 5. 基於類別的 React 元件和函數式 React 元件有什麼不同? 基於類別的元件和函數式元件之間的主要區別在於**它們的定義方式以及它們使用的語法。** 基於類別的元件被定義為 ES6 類別並擴展了 `React.Component` 類別。他們使用「render」方法傳回定義元件輸出的 JSX (JavaScript XML)。類別元件可以透過「this.state」和「this.setState()」存取元件生命週期方法和狀態管理。 ``` class App extends React.Component { state = { value: 0, }; handleAgeChange = () => { this.setState({ value: this.state.value + 1 }); }; render() { return ( <> <p>Value is {this.state.value}</p> <button onClick={this.handleAgeChange}> Increment value </button> </> ); } } ``` 另一方面,函數元件被定義為簡單的 JavaScript 函數。他們接受 props 作為參數並直接返回 JSX。功能元件無權存取生命週期方法或狀態。然而,隨著 React 16.8 中 React Hooks 的引入,功能元件現在可以管理狀態並使用其他功能,例如上下文和效果。 ``` import { useState } from 'react'; const App = () => { const [value, setValue] = useState(0); const handleAgeChange = () => { setValue(value + 1); }; return ( <> <p>Value is {value}</p> <button onClick={handleAgeChange}> Increment value </button> </> ); } ``` 一般來說,功能元件被認為更簡單、更容易閱讀和測試。建議盡可能使用函數式元件,除非有特定需要基於類別的元件。 ### 6. 元件的生命週期方法有哪些? 生命週期方法是一種掛鉤元件生命週期不同階段的方法,可讓您在特定時間執行特定程式碼。 以下是主要生命週期方法的清單: 1. `constructor`:這是建立元件時呼叫的第一個方法。它用於初始化狀態和綁定事件處理程序。在功能元件中,您可以使用“useState”鉤子來實現類似的目的。 2. `render`:此方法負責渲染 JSX 標記並傳回螢幕上要顯示的內容。 3. `componentDidMount`:元件在 DOM 中渲染後立即呼叫該方法。它通常用於初始化任務,例如 API 呼叫或設定事件偵聽器。 4. `componentDidUpdate`:當元件的 props 或 state 改變時呼叫該方法。它允許您執行副作用、根據更改更新元件或觸發其他 API 呼叫。 5. `componentWillUnmount`:在元件從 DOM 刪除之前呼叫此方法。它用於清理在`componentDidMount`中設定的任何資源,例如刪除事件偵聽器或取消計時器。 一些生命週期方法,例如“componentWillMount”、“componentWillReceiveProps”和“componentWillUpdate”,已被棄用或替換為替代方法或掛鉤。 至於“this”,它指的是類別元件的當前實例。它允許您存取元件內的屬性和方法。在函數式元件中,不使用“this”,因為函數未綁定到特定實例。 ### 7. 使用 useState 有什麼特色? `useState` 傳回一個狀態值和一個更新它的函數。 ``` const [value, setValue] = useState('Some state'); ``` 在初始渲染期間,傳回的狀態與作為第一個參數傳遞的值相符。 `setState` 函數用於更新狀態。它採用新的狀態值作為參數,並**對元件的重新渲染進行排隊**。 `setState` 函數也可以接受回呼函數作為參數,該函數將先前的狀態值作為參數。 [了解更多](https://react.dev/reference/react/useState) ### 8. 使用 useEffect 有什麼特別之處? `useEffect` 鉤子可讓您在功能元件中執行副作用。 稱為 React 渲染階段的功能元件的主體內部不允許突變、訂閱、計時器、日誌記錄和其他副作用。這可能會導致用戶介面中出現令人困惑的錯誤和不一致。 相反,建議使用 useEffect。傳遞給 useEffect 的函數將在渲染提交到螢幕後執行,或者如果您傳遞一組依賴項作為第二個參數,則每次依賴項之一發生變更時都會呼叫該函數。 ``` useEffect(() => { console.log('Logging something'); }, []) ``` [了解更多](https://react.dev/reference/react/useEffect) ### 9. 如何追蹤功能元件的卸載? 通常,「useEffect」會建立在元件離開畫面之前需要清理或重設的資源,例如訂閱或計時器辨識碼。 為了做到這一點,傳遞給`useEffect`的函數可以傳回一個**清理函數**。清理函數在元件從使用者介面刪除之前執行,以防止記憶體洩漏。此外,如果元件渲染多次(通常是這種情況),則在執行下一個效果之前會清除上一個效果。 ``` useEffect(() => { function handleChange(value) { setValue(value); } SomeAPI.doFunction(id, handleChange); return function cleanup() { SomeAPI.undoFunction(id, handleChange); }; }) ``` ### 10. React 中的 props 是什麼? Props 是從父元件傳遞給元件的資料。道具 是唯讀的,無法更改。 ``` // Parent component const Parent = () => { const data = "Hello, World!"; return ( <div> <Child data={data} /> </div> ); }; // Child component const Child = ({ data }) => { return <div>{data}</div>; }; ``` [了解更多](https://react.dev/learn/passing-props-to-a-component) ### 11. 什麼是狀態管理器?您曾與哪些狀態管理器合作過或認識哪些狀態管理器? 狀態管理器是幫助管理應用程式狀態的工具或程式庫。它提供了一個集中式儲存或容器來儲存和管理可由應用程式中的不同元件存取和更新的資料。 狀態管理器可以解決幾個問題。首先,將資料和與其相關的邏輯與元件分開是一個很好的做法。其次,當使用本機狀態並在元件之間傳遞它時,由於元件可能存在深層嵌套,程式碼可能會變得複雜。透過擁有全域存儲,我們可以存取和修改來自任何元件的資料。 除了 React Context,Redux 或 MobX 通常用作狀態管理庫。 [了解更多](https://mobx.js.org/README.html) [了解更多](https://redux-toolkit.js.org/) ### 12. 在什麼情況下可以使用本地狀態,什麼時候應該使用全域狀態? 如果本機狀態僅在一個元件中使用且不打算將其傳遞給其他元件,則建議使用本機狀態。本地狀態也用在表示清單中單一專案的元件中。但是,如果元件分解涉及嵌套元件且資料沿層次結構傳遞,則最好使用全域狀態。 ### 13. Redux中的reducer是什麼,它需要哪些參數? 減速器是一個純函數,它將狀態和操作作為參數。在減速器內部,我們追蹤接收到的操作的類型,並根據它修改狀態並傳回一個新的狀態物件。 ``` export default function appReducer(state = initialState, action) { // The reducer normally looks at the action type field to decide what happens switch (action.type) { // Do something here based on the different types of actions default: // If this reducer doesn't recognize the action type, or doesn't // care about this specific action, return the existing state unchanged return state } } ``` [了解更多](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers) ### 14. 什麼是操作以及如何更改 Redux 中的狀態? Action 是一個簡單的 JavaScript 物件,必須有一個字段 一種。 ``` { type: "SOME_TYPE" } ``` 您也可以選擇新增一些資料作為**有效負載**。為了 改變狀態,需要呼叫我們傳遞給它的調度函數 行動 ``` { type: "SOME_TYPE", payload: "Any payload", } ``` [了解更多](https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers) ### 15. Redux 實作了哪一種模式? Redux 實作了 **Flux 模式**,這是應用程式可預測的狀態管理模式。它透過引入單向資料流和應用程式狀態的集中儲存來幫助管理應用程式的狀態。 [了解更多](https://www.newline.co/fullstack-react/30-days-of-react/day-18/#:~:text=Flux%20is%20a%20pattern%20for,default% 20method %20用於%20處理%20資料。) ### 16. Mobx 實作哪一種模式? Mobx 實作了**觀察者模式**,也稱為發布-訂閱模式。 [了解更多](https://www.patterns.dev/posts/observer-pattern) ### 17. 使用 Mobx 的特徵是什麼? Mobx 提供了「observable」和「compulated」等裝飾器來定義可觀察狀態和反應函數。以action修飾的動作用於修改狀態,確保追蹤所有變更。 Mobx 還提供自動依賴追蹤、不同類型的反應、對反應性的細粒度控制,以及透過 mobx-react 套件與 React 無縫整合。總體而言,Mobx 透過根據可觀察狀態的變化自動執行更新過程來簡化狀態管理。 ### 18.如何存取Mobx狀態下的變數? 您可以透過使用「observable」裝飾器將變數定義為可觀察來存取狀態中的變數。這是一個例子: ``` import { observable, computed } from 'mobx'; class MyStore { @observable myVariable = 'Hello Mobx'; @computed get capitalizedVariable() { return this.myVariable.toUpperCase(); } } const store = new MyStore(); console.log(store.capitalizedVariable); // Output: HELLO MOBX store.myVariable = 'Hi Mobx'; console.log(store.capitalizedVariable); // Output: HI MOBX ``` 在此範例中,使用“observable”裝飾器將“myVariable”定義為可觀察物件。然後,您可以使用“store.myVariable”存取該變數。對「myVariable」所做的任何變更都會自動觸發相關元件或反應的更新。 [了解更多](https://mobx.js.org/actions.html) ### 19.Redux 和 Mobx 有什麼差別? Redux 是一個更簡單、更固執己見的狀態管理庫,遵循嚴格的單向資料流並促進不變性。它需要更多的樣板程式碼和顯式更新,但與 React 具有出色的整合。 另一方面,Mobx 提供了更靈活、更直觀的 API,且樣板程式碼更少。它允許您直接修改狀態並自動追蹤更改以獲得更好的性能。 Redux 和 Mobx 之間的選擇取決於您的特定需求和偏好。 ### 20.什麼是 JSX? 預設情況下,以下語法用於在 React 中建立元素。 ``` const someElement = React.createElement( 'h3', {className: 'title__value'}, 'Some Title Value' ); ``` 但我們已經習慣這樣看 ``` const someElement = ( <h3 className='title__value'>Some Title Value</h3> ); ``` 這正是標記所謂的 jsx。這是一種語言的擴展 簡化對程式碼和開發的認知 [了解更多](https://react.dev/learn/writing-markup-with-jsx#jsx-putting-markup-into-javascript) ### 21.什麼是道具鑽探? Props 鑽取是指透過多層嵌套元件傳遞 props 的過程,即使某些中間元件不直接使用這些 props。這可能會導致程式碼結構複雜且繁瑣。 ``` // Parent component const Parent = () => { const data = "Hello, World!"; return ( <div> <ChildA data={data} /> </div> ); }; // Intermediate ChildA component const ChildA = ({ data }) => { return ( <div> <ChildB data={data} /> </div> ); }; // Leaf ChildB component const ChildB = ({ data }) => { return <div>{data}</div>; }; ``` 在此範例中,「data」屬性從 Parent 元件傳遞到 ChildA,然後從 ChildA 傳遞到 ChildB,即使 ChildA 不會直接使用該屬性。當存在許多層級的嵌套或當元件樹中更靠下的元件需要存取資料時,這可能會成為問題。它會使程式碼更難維護和理解。 可以透過使用其他模式(如上下文或狀態管理庫(如 Redux 或 MobX))來緩解 Props 鑽探。這些方法允許元件存取資料,而不需要透過每個中間元件傳遞 props。 ### 22. 如何有條件地渲染元素? 您可以使用任何條件運算符,包括三元。 ``` return ( <div> {isVisible && <span>I'm visible!</span>} </div> ); ``` ``` return ( <div> {isOnline ? <span>I'm online!</span> : <span>I'm offline</span>} </div> ); ``` ``` if (isOnline) { element = <span>I'm online!</span>; } else { element = <span>I'm offline</span>; } return ( <div> {element} </div> ); ``` [了解更多](https://react.dev/learn/conditional-rendering) ### 23. useMemo 的用途是什麼?它是如何運作的? `useMemo` 用於緩存和記憶 計算結果。 傳遞建立函數和依賴項陣列。只有當任何依賴項的值發生變更時,`useMemo` 才會重新計算記憶值。此優化有助於避免 每次渲染都需要昂貴的計算。 使用第一個參數,函數接受執行計算的回調,使用第二個依賴項陣列,僅當至少一個依賴項發生變更時,該函數才會重新執行計算。 ``` const memoValue = useMemo(() => computeFunc(paramA, paramB), [paramA, paramB]); ``` [了解更多](https://react.dev/reference/react/useMemo) ### 24. useCallback 的用途是什麼?它是如何運作的? `useCallback` 掛鉤將傳回回呼的記憶版本,僅當依賴項之一的值發生變更時,該版本才會變更。 當將回調傳遞給依賴連結相等性來防止不必要的渲染的最佳化子元件時,這非常有用。 ``` const callbackValue = useCallback(() => computeFunc(paramA, paramB), [paramA, paramB]); ``` [了解更多](https://react.dev/reference/react/useCallback) ### 25. useMemo 和 useCallback 有什麼不同? 1. `useMemo` 用於儲存計算結果,而 `useCallback` 用於儲存函數本身。 2. `useMemo` 快取計算值,如果依賴項沒有改變,則在後續渲染時傳回它。 3. `useCallback` 快取函數本身並傳回相同的實例,除非相依性發生變更。 ### 26.什麼是 React Context? React Context 是一項功能,它提供了一種透過元件樹傳遞資料的方法,而無需在每個層級手動傳遞 props。它允許您建立一個全域狀態,樹中的任何元件都可以存取該狀態,無論其位置如何。當您需要在未透過 props 直接連接的多個元件之間共用資料時,上下文就非常有用。 React Context API 由三個主要部分組成: 1. `createContext`:此函數用於建立一個新的上下文物件。 2. `Context.Provider`:該元件用於向上下文提供值。它包裝了需要存取該值的元件。 3. `Context.Consumer` 或 `useContext` 鉤子:此元件或鉤子用於使用上下文中的值。它可以在上下文提供者內的任何元件中使用。 透過使用 React Context,您可以避免道具鑽探(透過多個層級的元件傳遞道具)並輕鬆管理更高層級的狀態,使您的程式碼更有組織性和效率。 [了解更多](https://react.dev/learn/passing-data-deeply-with-context) ### 27. useContext 的用途是什麼?它是如何運作的? 在典型的 React 應用程式中,資料使用 props 從上到下(從父元件到子元件)傳遞。但這樣的使用方法對於某些類型的道具來說可能過於繁瑣 (例如,所選語言、UI 主題),必須傳遞給應用程式中的許多元件。上下文提供了一種在元件之間共享此類資料的方法,而無需明確傳遞 props 樹的每一層。 呼叫 useContext 的元件將始終在以下情況下重新渲染: 上下文值發生變化。如果重新渲染元件的成本很高,您可以使用記憶來優化它。 ``` const App = () => { const theme = useContext(ThemeContext); return ( <div style={{ color: theme.palette.primary.main }}> Some div </div> ); } ``` [了解更多](https://react.dev/reference/react/useContext) ### 28. useRef 的用途是什麼?它是如何運作的? `useRef` 傳回一個可修改的 ref 物件,一個屬性。其中的當前值由傳遞的參數初始化。傳回的物件將在元件的整個生命週期內持續存在,並且不會因渲染而改變。 通常的用例是以命令式存取後代 風格。 IE。使用 ref,我們可以明確引用 DOM 元素。 ``` const App = () => { const inputRef = useRef(null); const buttonClick = () => { inputRef.current.focus(); } return ( <> <input ref={inputRef} type="text" /> <button onClick={buttonClick}>Focus on input tag</button> </> ) } ``` [了解更多](https://react.dev/reference/react/useRef) ### 29. 什麼是 React.memo()? `React.memo()` 是一個高階元件。如果您的元件始終使用不變的 props 渲染相同的內容,您可以將其包裝在「React.memo()」呼叫中,以在某些情況下提高效能,從而記住結果。這意味著 React 將使用上次渲染的結果,避免重新渲染。 `React.memo()` 只影響 props 的變更。如果一個功能元件被包裝在 React.memo 中並使用 useState、useReducer 或 useContext,那麼當狀態或上下文發生變化時,它將重新渲染。 ``` import { memo } from 'react'; const MemoComponent = memo(MemoComponent = (props) => { // ... }); ``` [了解更多](https://react.dev/reference/react/memo) ### 30.React Fragment是什麼? 從元件傳回多個元素是 React 中的常見做法。片段可讓您形成子元素列表,而無需在 DOM 中建立不必要的節點。 ``` <> <OneChild /> <AnotherChild /> </> // or <React.Fragment> <OneChild /> <AnotherChild /> </React.Fragment> ``` [了解更多](https://react.dev/reference/react/Fragment) ### 31.什麼是 React Reconciliation? 協調是一種 React 演算法,用於區分一棵元素樹與另一棵元素樹,以確定需要替換的部分。 協調是我們過去所說的虛擬 DOM 背後的演算法。這個定義聽起來是這樣的:當你渲染一個 React 應用程式時,描述該應用程式的元素樹是在保留的記憶體中產生的。然後這棵樹被包含在渲染環境中——例如,瀏覽器應用程式,它被翻譯成一組 DOM 操作。當應用程式狀態更新時,會產生一棵新樹。將新樹與前一棵樹進行比較,以便準確計算並啟用重繪更新的應用程式所需的操作。 [了解更多](https://react.dev/learn/preserving-and-resetting-state) ### 32.為什麼使用map()時需要列表中的鍵? 這些鍵可幫助 React 確定哪些元素已更改, 新增或刪除。必須指定它們以便 React 可以匹配 隨著時間的推移陣列元素。選擇鍵的最佳方法是使用能夠清楚區分清單專案與其鄰居的字串。大多數情況下,您將使用資料中的 ID 作為金鑰。 ``` const languages = [ { id: 1, lang: "JavaScript", }, { id: 2, lang: "TypeScript", }, { id: 3, lang: "Python", }, ]; const App = () => { return ( <div> <ul>{languages.map((language) => ( <li key={`${language.id}_${language.lang}`}>{language.lang}</li> ))} </ul> </div> ); } ``` [了解更多](https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key) ### 33. 如何在 Redux Thunk 中處理非同步操作? 要使用 Redux Thunk,您需要將其作為中間件導入。動作建立者不僅應該傳回一個物件,還應該傳回以調度為參數的函數。 ``` export const addUser = ({ firstName, lastName }) => { return dispatch => { dispatch(addUserStart()); } axios.post('https://jsonplaceholder.typicode.com/users', { firstName, lastName, completed: false }) .then(res => { dispatch(addUserSuccess(res.data)); }) .catch(error => { dispatch(addUserError(error.message)); }) } ``` [了解更多](https://redux.js.org/usage/writing-logic-thunks) ### 34.如何追蹤功能元件中物件欄位的變化? 為此,您需要使用“useEffect”掛鉤並將物件的欄位作為依賴項陣列傳遞。 ``` useEffect(() => { console.log('Changed!') }, [obj.someField]) ``` ### 35.如何存取DOM元素? 引用是使用 React.createRef() 或 useRef() 鉤子建立的,並透過 ref 屬性附加到 React 元素。透過存取已建立的引用,我們可以使用「ref.current」來存取 DOM 元素。 ``` const App = () => { const myRef = useRef(null); const handleClick = () => { console.log(myRef.current); // Accessing the DOM element }; return ( <div> <input type="text" ref={myRef} /> <button onClick={handleClick}>Click Me</button> </div> ); } export default App; ``` ### 36.什麼是自訂鉤子? 自訂鉤子是一個允許您在不同元件之間重複使用邏輯的功能。它是一種封裝可重複使用邏輯的方法,以便可以在多個元件之間輕鬆共用和重複使用。自訂掛鉤是通常以 **use ** 開頭的函數,並且可以根據需要呼叫其他掛鉤。 [了解更多](https://react.dev/learn/reusing-logic-with-custom-hooks) ### 37.什麼是公共API? 在索引檔案的上下文中,公共 API 通常是指向外部模組或元件公開並可存取的介面或函數。 以下是表示公共 API 的索引檔案的程式碼範例: ``` // index.js export function greet(name) { return `Hello, ${name}!`; } export function calculateSum(a, b) { return a + b; } ``` 在此範例中,index.js 檔案充當公共 API,其中導出函數“greet()”和“calculateSum()”,並且可以透過匯入它們從其他模組存取它們。其他模組可以導入並使用這些函數作為其實現的一部分: ``` // main.js import { greet, calculateSum } from './index.js'; console.log(greet('John')); // Hello, John! console.log(calculateSum(5, 3)); // 8 ``` 透過從索引檔案匯出特定函數,我們定義了模組的公共 API,允許其他模組使用這些函數。 ### 38. 建立自訂鉤子的規則是什麼? 1. 鉤子名稱以「use」開頭。 2. 如果需要,使用現有的鉤子。 3. 不要有條件地呼叫鉤子。 4. 將可重複使用邏輯提取到自訂掛鉤中。 5. 自訂hook必須是純函數。 6. 自訂鉤子可以傳回值或其他鉤子。 7. 描述性地命名自訂掛鉤。 [了解更多](https://react.dev/learn/reusing-logic-with-custom-hooks) ### 39.什麼是SSR(伺服器端渲染)? 伺服器端渲染(SSR)是一種用於在伺服器上渲染頁面並將完整渲染的頁面傳送到客戶端進行顯示的技術。它允許伺服器產生網頁的完整 HTML 標記(包括其動態內容),並將其作為對請求的回應傳送到客戶端。 在傳統的用戶端渲染方法中,用戶端接收最小的 HTML 頁面,然後向伺服器發出額外的資料和資源請求,這些資料和資源用於在客戶端渲染頁面。這可能會導致初始頁面載入時間變慢,並對搜尋引擎優化 (SEO) 產生負面影響,因為搜尋引擎爬蟲很難對 JavaScript 驅動的內容建立索引。 透過 SSR,伺服器透過執行必要的 JavaScript 程式碼來產生最終的 HTML 來負責渲染網頁。這意味著客戶端從伺服器接收完全呈現的頁面,從而減少了額外資源請求的需要。 SSR 縮短了初始頁面載入時間,並允許搜尋引擎輕鬆索引內容,從而實現更好的 SEO。 SSR 通常用於框架和函式庫中,例如用於 React 的 Next.js 和用於 Vue.js 的 Nuxt.js,以啟用伺服器端渲染功能。這些框架為您處理伺服器端渲染邏輯,讓實作 SSR 變得更加容易。 ### 40.使用SSR有什麼好處? 1. **改進初始載入時間**:SSR 允許伺服器將完全渲染的 HTML 頁面傳送到客戶端,從而減少客戶端所需的處理量。這可以縮短初始載入時間,因為使用者可以更快地看到完整的頁面。 2. **SEO友善**:搜尋引擎可以有效地抓取和索引SSR頁面的內容,因為完全渲染的HTML在初始回應中可用。這提高了搜尋引擎的可見度並有助於更好的搜尋排名。 3. **可存取性**:SSR 確保禁用 JavaScript 或使用輔助技術的使用者可以存取內容。透過在伺服器上產生 HTML,SSR 為所有使用者提供可靠且易於存取的使用者體驗。 4. **低頻寬環境下的效能**:SSR減少了客戶端需要下載的資料量,有利於低頻寬或高延遲環境下的使用者。這對於行動用戶或網路連線速度較慢的用戶尤其重要。 雖然 SSR 提供了這些優勢,但值得注意的是,與客戶端渲染方法相比,它可能會帶來更多的伺服器負載和維護複雜性。應仔細考慮快取、可擴展性和伺服器端渲染效能最佳化等因素。 ### 41.你知道Next.js的主要功能有哪些? 1. `getStaticProps`:此方法用於在建置時取得資料並將頁面預先渲染為靜態 HTML。它確保資料在建置時可用,並且不會因後續請求而更改。 ``` export async function getStaticProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } ``` 2. `getServerSideProps`:此方法用於在每個請求上取得資料並在伺服器上預先渲染頁面。當您需要取得可能經常變更或特定於使用者的資料時,可以使用它。 ``` export async function getServerSideProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } ``` 3. `getStaticPaths`:此方法在動態路由中使用,用於指定建置時應預先渲染的路徑清單。它通常用於獲取帶有參數的動態路由的資料。 ``` export async function getStaticPaths() { const res = await fetch('https://api.example.com/posts'); const posts = await res.json(); const paths = posts.map((post) => ({ params: { id: post.id } })); return { paths, fallback: false }; } ``` [了解更多](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating) ### 42.什麼是 Linters? Linters 是用來檢查原始程式碼是否有潛在錯誤、錯誤、風格不一致和可維護性問題的工具。它們可幫助執行編碼標準並確保整個程式碼庫的程式碼品質和一致性。 Linters 的工作原理是掃描原始程式碼並將其與一組預先定義的規則或指南進行比較。這些規則可以包括語法和格式約定、最佳實踐、潛在錯誤和程式碼異味。當 linter 發現違反規則時,它會產生警告或錯誤,突出顯示需要注意的特定行或多行程式碼。 使用 linter 可以帶來幾個好處: 1. **程式碼品質**:Linter 有助於辨識和防止潛在的錯誤、程式碼異味和反模式,從而提高程式碼品質。 2. **一致性**:Linter 強制執行編碼約定和風格指南,確保整個程式碼庫的格式和程式碼結構一致,即使多個開發人員正在處理同一個專案時也是如此。 3. **可維護性**:透過儘早發現問題並促進良好的編碼實踐,linter 有助於程式碼的可維護性,使程式碼庫更容易理解、修改和擴展。 4. **效率**:Linter 可以透過自動化程式碼審查流程並在常見錯誤在開發或生產過程中引起問題之前捕獲它們來節省開發人員的時間。 一些流行的 linter 包括用於 JavaScript 的 ESLint 以及用於 CSS 和 Sass 的 Stylelint。 [了解更多](https://eslint.org/docs/latest/use/getting-started) ### 43.你知道哪些 React 架構解決方案? 有多種用於建立 React 專案的架構解決方案和模式。一些受歡迎的包括: 1. **MVC(模型-視圖-控制器)**:MVC 是一種傳統的架構模式,它將應用程式分為三個主要元件 - 模型、視圖和控制器。 React 可以在 View 層中使用來渲染 UI,而其他程式庫或框架可以用於 Model 和 Controller 層。 2. **Flux**:Flux是Facebook專門針對React應用程式所推出的應用架構。它遵循單向資料流,其中資料沿著單一方向流動,從而更容易理解和除錯應用程式的狀態變更。 3. **原子設計**:原子設計並不是React特有的,而是將UI分割成更小、可重複使用元件的設計方法。它鼓勵建立小型、獨立且可以組合以建立更複雜的 UI 的元件。 4. **容器和元件模式**:此模式將表示(元件)與邏輯和狀態管理(容器)分開。元件負責渲染 UI,而容器則處理業務邏輯和狀態管理。 5. **功能切片設計**:它是一種用於組織和建構 React 應用程式的現代架構方法。它旨在透過根據功能或模組劃分應用程式程式碼庫來解決可擴展性、可維護性和可重用性的挑戰。 ### 44.什麼是特徵切片設計? 它是一種用於組織和建立 React 應用程式的現代架構方法。它旨在透過根據功能或模組劃分應用程式程式碼庫來解決可擴展性、可維護性和可重用性的挑戰。 在功能切片設計中,應用程式的每個功能或模組都組織到一個單獨的目錄中,其中包含所有必要的元件、操作、reducers 和其他相關檔案。這有助於保持程式碼庫的模組化和隔離性,使其更易於開發、測試和維護。 功能切片設計促進了關注點的清晰分離,並將功能封裝在各個功能中。這允許不同的團隊或開發人員獨立地處理不同的功能,而不必擔心衝突或依賴性。 ![功能切片設計](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amysbtftfjkuss87yu8v.png) **我強烈建議點擊“了解更多”按鈕以了解功能切片設計** [了解更多](https://dev.to/m_midas/feature-sliced-design-the-best-frontend-architecture-4noj) ## 了解更多 如果您還沒有閱讀過,我強烈建議您閱讀我關於前端面試問題的其餘文章。 https://dev.to/m_midas/52-frontend-interview-questions-javascript-59h6 https://dev.to/m_midas/41-frontend-interview-questions-css-4imc https://dev.to/m_midas/15-most-common-frontend-interview-questions-4njp ## 結論 總之,面試 React 前端開發人員職位需要對框架的核心概念、原理和相關技術有深入的了解。透過準備本文中討論的問題,您可以展示您的 React 知識並展示您建立高效且可維護的使用者介面的能力。請記住,不僅要專注於記住答案,還要理解基本概念並能夠清楚地解釋它們。 此外,請記住,面試不僅涉及技術方面,還旨在展示您解決問題的能力、溝通能力以及團隊合作能力。透過將技術專業知識與強大的整體技能相結合,您將具備在 React 前端開發人員面試中脫穎而出的能力,並在這個令人興奮且快速發展的領域找到您夢想的工作。 祝你好運!

自己用 20 行 JavaScript 來寫螢幕錄影功能!

假設您厭倦了螢幕錄影機的付費專區和限制,並想繼續編寫自己的程式碼 - 事實證明您已經可以透過幾行程式碼獲得基本功能。 原文出處:https://dev.to/ninofiliu/simple-screen-recorder-in-20-lines-of-javascript-4ina 我們可以要求瀏覽器使用[螢幕擷取API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture)為我們取得擷取視訊串流,但出於安全原因我們必須確保[使用者手勢](https://stackoverflow.com/q/56388258/8186898)觸發捕獲,例如點擊按鈕: ``` const button = document.createElement("button"); button.innerHTML = "capture"; document.body.append(button); button.addEventListener("click", async () => { // TODO }); ``` 點擊後,獲取視訊串流並進行錄製 ``` const stream = await navigator.mediaDevices.getDisplayMedia(); const recoder = new MediaRecorder(stream); recoder.start(); ``` 當使用者停止分享螢幕時停止錄製 ![圖片描述](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3k0wtf5udsvefz3hl88e.png) ``` const [video] = stream.getVideoTracks(); video.addEventListener("ended", () => { recoder.stop(); }); ``` 取得錄音檔案並下載 ``` recoder.addEventListener("dataavailable", (evt) => { const a = document.createElement("a"); a.href = URL.createObjectURL(evt.data); a.download = "capture.webm"; a.click(); }); ``` 瞧,你就有了一個簡單的螢幕錄影機! 它有很多解決起來很有趣的限制- 音頻錄製、網絡攝像頭集成、長時間執行的流等- 但我剛剛發現用這麼少的程式碼行就可以完成如此強大的事情,這太棒了,無法不分享。 [codepen連結](https://codepen.io/ninofiliu/embed/BaMzxQM)

8款新潮好用的 React 套件分享

原文出處:https://dev.to/github20k/8-components-to-become-a-react-master-2ee4 # 簡介 我收集了可用於建立終極網站的 React 元件。 他們每個人都有獨特的用例! 別忘了給他們加星號🌟 那麼就讓我們開始吧! ![讓我們開始吧](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gxaa9vnqc8ye5a0g1iv2.gif) --- ## 1. [Clickvote](https://github.com/clickvote/clickvote?utm_source=dev&utm_medium=react-component-expert) - 按讚、投票並查看任何上下文。 👑 [![Clickvote](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yn237newdjgm4uhl1p1.png)](https://github.com/clickvote/clickvote?utm_source=dev&utm_medium=react-clickvote/clickvote?utm_source=dev&utm_medium=react-元件專家) 將點讚和評論元件無縫整合到您的應用程式中。 使用這個簡單的 React 程式碼渲染喜歡框、評論框和反應元件! ``` import { ClickVoteProvider } from '@clickvote/react'; import { ClickVoteComponent } from '@clickvote/react'; import { LikeStyle } from '@clickvote/react'; <ClickVoteProvider> <ClickVoteComponent id={CONTEXT} voteTo={ID}> {(props) => <LikeStyle {...props} />} </ClickVoteComponent> </ClickVoteProvider> ``` 在這裡加星號🌟 [https://github.com/clickvote/clickvote](https://github.com/clickvote/clickvote?utm_source=dev&utm_medium=react-component-expert) --- ## 2. [Novu](https://github.com/novuhq/novu) - 將應用程式內通知新增至您的應用程式! [![Novu](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wyb47s6sbh232x77f794.png)](https://github.com/novuhq/novu) 用於在一個地方管理所有通訊管道的簡單元件和 API:電子郵件、SMS、Direct 和 Push 您可以使用此 React 元件為您的應用程式新增應用程式內通知 ``` import { NovuProvider, PopoverNotificationCenter, NotificationBell, IMessage, } from "@novu/notification-center"; <NovuProvider subscriberId={"SUBSCRIBER_ID"} applicationIdentifier={"APPLICATION_IDENTIFIER"} > <PopoverNotificationCenter colorScheme="dark"> {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />} </PopoverNotificationCenter> </NovuProvider> ``` 在這裡加星號🌟 https://github.com/novuhq/novu     ## 3. [CopilotKit](https://github.com/RecursivelyAI/CopilotKit) - 使用 GPT 將自動補全功能加入您的文字! [![CopilotKit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bvi730jwuggjti7peg8.png)](https://github.com/RecursivelyAI/CopilotKit) 適用於任何 React 應用程式的強大且可破解的副駕駛。 只需幾分鐘即可開始並無限迭代。 一個簡單的元件,可以自動完成您的內容,就像您在 Gmail 上一樣。 ``` import { CopilotTextarea } from "@copilotkit/react-textarea"; import { CopilotProvider } from "@copilotkit/react-core"; <CopilotProvider> <CopilotTextarea/> </CopilotProvider> ``` 在這裡加星號🌟 https://github.com/RecursivelyAI/CopilotKit     ## 4. [Tolgee](https://github.com/tolgee/tolgee-platform) - 將任何上下文翻譯成任何語言! [![Tolgee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbsuotaz842jjgaduqe3.png)](https://github.com/tolgee/tolgee-platform) 智慧平台、快速整合、輕鬆在地化。 一個簡單的元件,可以將任何上下文翻譯成任何語言! ``` import { TolgeeProvider, T } from "@tolgee/react"; <TolgeeProvider tolgee={tolgee} fallback="Loading..." // loading fallback > <T keyName="translate_me">Translate me!</T> </TolgeeProvider> ``` 在這裡加星號🌟 https://github.com/tolgee/tolgee-platform     ## 5. [Hanko](https://github.com/teamhanko/hanko) - 對您的應用程式進行金鑰身份驗證! [![Hanko](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1j2aa3um0umgxskgau7u.png)](https://github.com/teamhanko/hanko) 美觀、可自訂的元件、SDK 和 API 可為您的登入和使用者管理提供支援。 5 分鐘內新增密碼驗證!這是該元件: ``` import { register } from "@teamhanko/hanko-elements"; const hankoApi = process.env.HANKO_API_URL; register(hankoApi); <hanko-auth />; ``` 在這裡加星號🌟 https://github.com/teamhanko/hanko     ## 6. [React Email](https://github.com/resendlabs/react-email) - 下一代書寫電子郵件 [![React 電子郵件](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ga9lcps91gv78sdy6l62.png)](https://github.com/resendlabs/react-email) 一系列高品質、無樣式的元件,用於使用 React 和 TypeScript 建立精美的電子郵件。 幫助您設計和發送電子郵件的元件! ``` import { Button } from '@react-email/button'; import { Html } from '@react-email/html'; <Html lang="en" dir="ltr"> <Button href="https://example.com"> Click me </Button> </Html> ``` 在這裡加星號🌟 https://github.com/resendlabs/react-email     ## 7. [React Flow](https://github.com/wbkd/react-flow) - 建立可拖曳工作流程的最佳方式! [![反應流程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zqcjbtlg5d8hx69vtvaa.png)](https://github.com/wbkd/react-flow) 高度可自訂的 React 元件,用於建立基於節點的編輯器和互動式圖表 一個簡單的元件,將為工作流程呈現拖放節點。 ``` import ReactFlow, { MiniMap, Controls, Background, useNodesState, useEdgesState, addEdge, } from 'reactflow'; <ReactFlow nodes={nodes} edges={edges} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} > <MiniMap /> <Controls /> <Background /> </ReactFlow> ``` 在這裡加星號🌟 https://github.com/wbkd/react-flow     ## 8. [Novel](https://github.com/steven-tey/novel) - 最好的所見即所得編輯器! [![反應流程](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dz1xewgmoc7htoq6d08b.png)](https://github.com/steven-tey/novel) 概念式所見即所得編輯器,具有人工智慧驅動的自動完成功能。 一個簡單的元件,將呈現具有大量功能的概念樣式編輯器! ``` import { Editor } from "novel"; <Editor /> ``` 在這裡加星號🌟 https://github.com/steven-tey/novel --- 謝謝你們! 下週見😎