✨ Snippets === 還有什麼 snippets 的想法都可以提出來 ## 📑 配置 Snippets 教學 ![image](https://hackmd.io/_uploads/HyPCLq3f0.png) ## 📑 配置 Tailwind 教學 在你的 settings.json 中插入以下設置 ``` json { "tailwindCSS.classAttributes": [ "class", "className", "ngClass", ".*ClassName", ".*Styles", ".*Class" ], "tailwindCSS.experimental.classRegex": [ "/*\\s?tw:\\s?\\*/\\s?[`|'|\"](.*?)[`|'|\"]", ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], ["className:[\\[|\\(]?([^)]*)[\\]|\\)]?", "(?:'|\"|`)([^\"'`]*)(?:'|\"|`)"] ], "editor.quickSuggestions": { "strings": "on" }, "editor.inlineSuggest.enabled": true, } ``` <br> ## 📑 Typescriptreact.json 📚 [snippet with filename](https://stackoverflow.com/questions/70785401/how-to-get-file-name-when-overriding-vs-code-snippet) 📚 [vscode snippets variables](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_variables) ```json { "create React FC": { "prefix": "rfc", "body": [ "\"use client\";\n", "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}: React.FC = () => {", " return (", " <div>${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}</div>", " )", "}" ] }, "create React FC Layout": { "prefix": "rfclayout", "body": [ "\"use client\";\n", "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}: React.FC<{ children: React.ReactNode }> = ({ children }) => {", " return (", " <div>${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}</div>", " )", "}" ] }, "create React Context with Context Helper": { "prefix": "rctxhelp", "body": [ "\"use client\";\n", "import { contextHelper } from '@nilswg/react-utils';", "import { useState } from 'react';\n", "const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = contextHelper({", " contextName: '${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}',", " contextValue(props) {", " const [toggle, setToggle] = useState<boolean>(false);", " return {", " toggle,", " setToggle,", " };", " },", "});\n", "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Provider = ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}.Provider;", "export const use${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}.useContext;" ] }, // "create React Context (跪求你用 rctxhelp)": { // "prefix": "rctx", // "body": [ // "\"use client\";\n", // "import type { FC, ReactNode } from 'react';", // "import { createContext, useContext, useMemo } from 'react';\n", // "type Context_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = {};\n", // "const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context = createContext<Context_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} | null>(null);\n", // "type Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}ContextProvider = {", // " children: ReactNode;", // "}\n", // "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}ContextProvider: FC<Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}ContextProvider> = ({ children }) => {", // " return (", // " <${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context.Provider", // " value={useMemo(() => {", // " return {};", // " }, [])}>", // " {children}", // " </${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context.Provider>", // " );", // "};\n", // "export const use${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = () => {", // " const context = useContext(${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Context);", // " if (!context) {", // " throw new Error('use${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} must be used within a ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Provider');", // " }", // " return context;", // "};" // ] // }, // "create React Composition Component (不好駕馭的 pattern)": { // "prefix": "rcc", // "body": [ // "\"use client\";\n", // "import type { FC, ReactNode } from 'react';", // "import {} from 'react';\n", // "type Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root = {", // " children: ReactNode;", // "};\n", // "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root: FC<Props_${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root> = ({ children }) => {", // " return <div>{children}</div>;", // "};\n", // "export const ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/} = Object.freeze({", // " Root: ${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}Root,", // "});" // ] // }, "create Next Page with default export": { "prefix": "nfc", "body": [ "", "const ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}Page: React.FC = () => {", " return (", " <div>${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}Page</div>", " )", "}\n", "export default ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}Page;" ] }, "create Next Layout Page with default export": { "prefix": "nfclayout", "body": [ "", "const ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}PagesLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {", " return <div>{children}</div>", "}\n", "export default ${TM_DIRECTORY/.*[\\\/](.*)/${1:/pascalcase}/}PagesLayout;" ] }, "create Next Slug Page with default export": { "prefix": "nfcslug", "body": [ "interface PageProps {", " params: Promise<{ $1: string }>;", "}\n", "const ${TM_DIRECTORY/.*[\\\\/](.*)[\\\\/]\\[.*\\]/${1:/pascalcase}/}Page: React.FC<PageProps> = async ({ params }) => {", " const { $1 } = await params;", " return <div>${TM_DIRECTORY/.*[\\\\/](.*)[\\\\/]\\[.*\\]/${1:/pascalcase}/} {$1} Page</div>", "}\n", "export default ${TM_DIRECTORY/.*[\\\\/](.*)[\\\\/]\\[.*\\]/${1:/pascalcase}/}Page;" ] }, "add className in props": { "prefix": "$cl", "body": "className?: string;" }, "add children in props": { "prefix": "$ch", "body": "children: React.ReactNode;" }, } ``` <br> 🛠️ Extensions === ![image](https://hackmd.io/_uploads/ByKZfUWB1g.png) > Cline: 純個人使用之 AI 工具可不裝 > Git Graph: 就算你有喜歡的 git 管理工具,但便於討論建議裝 <br> 📝 Coding Pattern === 目前我們開發上,求個開發方便,可以先直覺書寫就好。 我提這幾個主要是我會這樣寫,溝通一下希望你們看得懂。 ### 📑 [React Compound-Pattern](https://www.patterns.dev/react/compound-pattern) ```typescript= import type { FC, ReactNode } from 'react'; import { createContext, useContext, useMemo, useState } from 'react'; type Context_Menu = { isOpen: boolean; toggle: () => void; }; const MenuContext = createContext<Context_Menu | null>(null); type Props_MenuContextProvider = { children: ReactNode; }; export const MenuContextProvider: FC<Props_MenuContextProvider> = ({ children }) => { const [isOpen, setIsOpen] = useState(false); const toggle = () => setIsOpen(!isOpen); return ( <MenuContext.Provider value={useMemo(() => { return { isOpen, toggle, }; }, [isOpen])}> {children} </MenuContext.Provider> ); }; export const useMenu = () => { const context = useContext(MenuContext); if (!context) { throw new Error('useMenu must be used within a MenuProvider'); } return context; }; const MenuInstance = () => { return ( <MenuWrapper> <MenuButton /> </MenuWrapper> ); }; const MenuWrapper: FC<{ children: ReactNode }> = ({ children }) => { return ( <MenuContextProvider> <div className="menu-wrapper">{children}</div> </MenuContextProvider> ); }; const MenuButton: FC = () => { const { isOpen, toggle } = useMenu(); return <button onClick={toggle}>{isOpen ? 'Close' : 'Open'}</button>; }; ``` <br/> > React Compound-Pattern 實現完全基於 React.Context > 然而,React.Context 雖然強大但前置動作繁瑣,推薦使用自製的 **ContextHelper** ## ContextHelper ```typescript= export const MenuContext = createProvider({ contextName: 'MenuContext', contextValue: (props: { foo: string; d: number }) => { const [isOpen, setIsOpen] = useState(false); const toggle = () => setIsOpen(!isOpen); return { isOpen, toggle }; // <== Provider }, }); export const useMenu = MenuContext.useContext; export const MenuProvider = MenuContext.Provider; ``` 更多說明 👉 https://hackmd.io/@nilswg/rJ99I1cqC <!-- ### 📑 [React Composition-Pattern](https://dev.to/ricardolmsilva/composition-pattern-in-react-28mj) ```typescript= type Props_PageRoot = { children: ReactNode; }; const PageRoot: FC<Props_PageRoot> = ({ children }) => { return <div>{children}</div>; }; type Props_PageChildRoot = { children: ReactNode; }; const PageChildRoot: FC<Props_PageChildRoot> = ({ children }) => { return <div>{children}</div>; }; type Props_PageChildItem = { children: ReactNode; }; const PageChildItem: FC<Props_PageChildItem> = ({ children }) => { return <div>{children}</div>; }; ``` 使用範例 ```typescript= export const Page = Object.freeze({ // Object.freeze 使物件不能竄改。 Root: PageRoot, Child: Object.freeze({ Root: PageChildRoot, Item: PageChildItem, }), }); const Page_Demo = () => { return ( <Page.Root> // 對比版本二,需要 .Root <Page.Child.Root> <Page.Child.Item>Item 1</Page.Child.Item> <Page.Child.Item>Item 2</Page.Child.Item> </Page.Child.Root> </Page.Root> ); }; ``` --> <br> # 完整的 setting.json 參考範例 > 大部分我本人都已經測試過有效,新專案可直接取代掉你目前的 setting.json > 或是,擷取你需要的部份直接貼上使用。 > 除了! 字體你要自己去下載,否則該字體配置會不合用 ```json { "editor.fontFamily": "Monoid Nerd Font Propo", // 字體 "editor.lineHeight": 1.75, "editor.letterSpacing": -0.5, "editor.fontLigatures": true, "editor.fontSize": 12.5, "editor.tabSize": 2, "editor.linkedEditing": true, "workbench.activityBar.location": "top", // 固定 vscode ActivityBar 位置 "workbench.colorTheme": "Default Dark Modern", "workbench.iconTheme": "material-icon-theme", "workbench.editor.customLabels.patterns": { "**/index.{ts,tsx}": "${dirname}/", "**/route.tsx": "${dirname} Route", "**/page.tsx": "${dirname} Page", "**/layout.tsx": "${dirname} Layout" }, // extension: TrailingWhitespace "files.trimTrailingWhitespace": true, // formatter 配置 "editor.defaultFormatter": "biomejs.biome", "[json]": { "editor.defaultFormatter": "vscode.json-language-features" }, // tailwind 配置 "tailwindCSS.classAttributes": [ "class", "className", "ngClass", ".*ClassName", ".*Styles", ".*Class" ], "tailwindCSS.experimental.classRegex": [ "/*\\s?tw:\\s?\\*/\\s?[`|'|\"](.*?)[`|'|\"]", [ "cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" ], [ "cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]" ], [ "className:[\\[|\\(]?([^)]*)[\\]|\\)]?", "(?:'|\"|`)([^\"'`]*)(?:'|\"|`)" ] ], "editor.quickSuggestions": { "strings": "on" }, } ``` <br> ## VSCode 的字體下載 https://www.nerdfonts.com/font-downloads ### 字體安裝教學 > 1.下載 (這款蠻多人用的) ![image](https://hackmd.io/_uploads/SkW0dW8qA.png) > => 2.解壓縮 => 3.安裝 ![image](https://hackmd.io/_uploads/r1uXYZI9C.png) > => 4. Vscode Setting => 5. 設置 "CaskaydiaCove Nerd Font" ![image](https://hackmd.io/_uploads/HJncFWUq0.png) <br> # 其他筆記(可略過) <br> ### TS 工具類型 ```ts // 顯示更完整的 TS 類型 export type Prettier<T> = { [Key in keyof T]: T[Key] extends { [key: string]: {} } ? Prettier<T[Key]> : T[Key]; } & {}; ``` <br> ### Auto Import File Exclude Patterns 因為 Shadcn 的底層是用到 radix-ui 有時候,你 import 會跳出兩個重複的名稱的套件,蠻煩的。 設置後,可以排除掉,不常用的 import file 路徑,也減少犯錯的可能性。 ![image](https://hackmd.io/_uploads/Hk5LbDOiA.png)