整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          2024 年,你應該知道的 5 種 React 設計模式

          者 | Lakindu Hewawasam

          譯者 | 許學文

          策劃 | 丁曉昀

          如果你在開發工作中使用的是 React 框架,那么首當其沖要學習的就是思考如何設計組件。組件設計并非簡單地將多個組件合成一個集合,而是需要思考如何設計更小、復用性更強的組件。例如,思考下面這張組件圖:



          簡化的組件圖


          圖中有三個組件,分別是:


          1. Typography 組件
          2. Footer 組件
          3. Sizeable Box 組件


          如圖所示,Typography 組件同時被 Footer 和 Sizeable Box 組件使用。通常我們以為這樣,就能構建一個簡單、易維護和易排除錯誤的應用了。但其實只是這樣思考組件的設計是遠遠不夠的。


          如果你知道如何從組件的視角思考問題,就可以通過在 React 組件中使用設計模式來提高代碼的整體模塊性、可擴展性和可維護性。


          因此,下面這五種設計模式,是你在使用 React 時必須要掌握的。


          模式一:基礎組件


          首先,在使用 React 時候,請嘗試為應用設計基礎組件。


          基礎UI組件,就是一個具備默認行為且支持定制化的組件。


          例如,每個應用都會通過基礎的樣式、按鈕設計或者基礎的排版,來實現應用在視覺和交互上的一致性。這些組件的設計特點是:


          1. 組件會應用一組默認的配置。因此,使用者無需進行任何額外的配置,就可以快速基于默認配置使用組件。
          2. 組件可以支持定制化,使用者通過定制化可以覆蓋組件的默認行為,從而為組件提供自定義的整體視覺和交互效果。


          通過一個 Button 組件就能很好地說明基礎組件模式的實現。示例如下:


          1. Button 組件可能會有諸如空心、實心等不同形態。
          2. Button 組件可能會有默認的文本。


          現在你就可以利用基礎組件模式進行設計,使組件的使用者可以改變其行為。請參考我基于基礎組件模式完成的Button組件,示例代碼如下:


          import React, { ButtonHTMLAttributes } from 'react';
          
          
          
          
          // 按鈕組件的形態:實心或者空心
          type ButtonVariant = 'filled' | 'outlined';
          
          
          
          
          export type ButtonProps = {
            /**
             * the variant of the button to use 
             * @default 'outlined'
             */
            variant?: ButtonVariant;
          } & ButtonHTMLAttributes<HTMLButtonElement>;;
          
          
          
          
          const ButtonStyles: { [key in ButtonVariant]: React.CSSProperties } = {
            filled: {
              backgroundColor: 'blue', // Change this to your filled button color
              color: 'white',
            },
            outlined: {
              border: '2px solid blue', // Change this to your outlined button color
              backgroundColor: 'transparent',
              color: 'blue',
            },
          };
          
          
          
          
          export function Button({ variant = 'outlined', children, style, ...rest }: ButtonProps) {
            return (
              <button
                type='button'
                style={{
                  ...ButtonStyles[variant],
                  padding: '10px 20px',
                  borderRadius: '5px',
                  cursor: 'pointer',
                  ...style
                }} {...rest}>
                {children}
              </button>
            );
          }

          復制代碼


          仔細觀察代碼會發現,這里 Button 組件的 props 類型合并了原生 HTML 中 button 標簽屬性的全部類型。這意味著,使用者除了可以為 Button 組件設置默認配置外,還可以設置諸如 onClick、aria-label 等自定義配置。這些自定義配置會通過擴展運算符傳遞給 Button 組件內部的 button 標簽。


          通過不同的上下文設置,可以看到不同的 Button 組件的形態,效果截圖如下圖。


          這個可以查看具體設置:

          https://bit.cloud/lakinduhewa/react-design-patterns/base/button/~compositions



          基礎組件在不同上下文中的使用效果


          通過不同的上下文,你可以設定組件的行為。這可以讓組件成為更大組件的基礎。


          模式二:組合組件


          在成功創建了基礎組件后,你可能會希望基于基礎組件創建一些新的組件。


          例如,你可以使用之前創建的 Button 組件來實現一個標準的 DeleteButton 組件。通過在應用中使用該 DeleteButton,可以讓應用中所有刪除操作在顏色、形態以及字體上保持一致。


          不過,如果出現重復組合一組組件來實現相同效果的現象,那么你可以考慮將它們封裝到一個組件中。


          下面,讓我們來看看其中一種實現方案:

          https://bit.cloud/lakinduhewa/react-design-patterns/composition/delete-button



          使用組合模式創建組件


          如上面的組件依賴圖所示,DeleteButton 組件使用基礎的 Button 組件為所有與刪除相關的操作提供標準的實現。下面是基本代碼實現:


          // 這里引入了,基礎按鈕組件和其props
          import { Button, ButtonProps } from '@lakinduhewa/react-design-patterns.base.button';
          import React from 'react';
          
          
          
          
          export type DeleteButtonProps = {} & ButtonProps;
          
          
          
          
          export function DeleteButton({ ...rest }: DeleteButtonProps) {
            return (
              <Button
                variant='filled'
                style={{
                  background: 'red',
                  color: 'white'
                }}
                {...rest}
              >
                DELETE
              </Button>
            );
          }

          復制代碼


          我們使用基于模式一創建的 Button 組件來實現的 DeleteButton 組件的效果如下:



          現在我們可以在應用中使用統一的刪除按鈕。此外,如果你使用類似 Bit 的構建系統進行組件的設計和構建,那么當 Button 組件發生改變時,可以讓CI服務自動將此改變傳遞到DeleteButton組件上,就像下面這樣(當 Button 組件從 0.0.3 升級到了 0.0.4,那么 CI 服務會自動觸發,將 DeleteButton 組件從 0.0.1 升級到 0.0.2):


          Bit 上的一個 CI 構建


          模式三:使用 Hooks


          React Hooks 是React v16就推出來的特性,它不依賴類組件實現狀態管理、負效應等概念。簡而言之,就是你可以通過利用 Hooks API 擺脫對類組件的使用需求。useSate 和 useEffect 是最廣為人知的兩個 Hooks API,但本文不打算討論它們,我想重點討論如何利用 Hooks 來提高組件的整體可維護性。


          例如,請考慮下面這個場景:


          1. 有一個 BlogList 組件。
          2. BlogList 組件會通過調用一個簡單的 API,獲取博客文章列表數據,同時將其渲染在組件上。


          基于上面的案例,你可能會像下面這樣將 API 邏輯直接寫在函數組件中:


          import React, { useState, useEffect } from 'react';
          import axios from 'axios';
          const BlogList = () => {
              const [blogs, setBlogs] = useState([]);
              const [isLoading, setIsLoading] = useState(true);
              const [error, setError] = useState(null);
              useEffect(() => {
                  axios.get('https://api.example.com/blogs')
                      .then(response => {
                          setBlogs(response.data);
                          setIsLoading(false);
                      })
                      .catch(error => {
                          setError(error);
                          setIsLoading(false);
                      });
              }, []);
              if (isLoading) return <div>Loading...</div>;
              if (error) return <div>Error: {error.message}</div>;
              return (
                  <div>
                      <h2>Blog List</h2>
                      <ul>
                          {blogs.map(blog => (
                              <li key={blog.id}>{blog.title}</li>
                          ))}
                      </ul>
                  </div>
              );
          };
          export default BlogList;

          復制代碼


          這樣寫,組件也能正常工作。它將會獲取博客文章列表并且渲染在 UI 上。但是,這里將 UI 邏輯和 API 邏輯混在一起了。


          理想情況下,React 組件應該不需要關系如何獲取數據。而只需要關心接收一個數據數組,然后將其呈現在 DOM 上。


          因此,實現這一目標的最佳方法是將 API 邏輯抽象到 React Hook 中,以便在組件內部進行調用。這樣做就可以打破 API 調用與組件之間的耦合。通過這種方式,就可以在不影響組件的情況下,修改底層的數據獲取邏輯。


          其中一種實現方式如下。


          1.useBlog hook


          import { useEffect, useState } from 'react';
          import { Blog } from './blog.type';
          import { Blogs } from './blog.mock';
          
          
          
          
          export function useBlog() {
            const [blogs, setBlogs] = useState<Blog[]>([]);
            const [loading, setLoading] = useState<boolean>(false);
          
          
          
          
            useEffect(() => {
              setLoading(true);
               // 注意:這里的setTimeout非實際需要,只是為了模擬API調用
              setTimeout(() => {
                setBlogs(Blogs);
                setLoading(false);
              }, 3000);
            }, []);
          
          
          
          
            return { blogs, loading }
          }

          復制代碼


          如上代碼所示,useBlog hook 獲取博客列表數據,然后賦值給狀態變量,最后通過導出變量給到消費者(BlogList 組件)使用:



          Hook 效果


          2.BlogList 組件


          import React from 'react';
          // 引入上面封裝的 useBlog hook
          import { useBlog } from '@lakinduhewa/react-design-patterns.hooks.use-blog';
          export function BlogList() {
            const { blogs, loading } = useBlog();
            if (loading) {
              return (
                <p>We are loading the blogs...</p>
              )
            }
            return (
              <ul>
                {blogs.map((blog) => <ol
                  key={blog.id}
                >
                  {blog.title}
                </ol>)}
              </ul>
            );
          }

          復制代碼



          BlogList 組件效果


          通過調用 useBlog 和使用其導出的狀態變量,我們在 BlogList 組件中使用了 Hooks。如此,相對于之前,我們可以減少大量代碼,并以最少的代碼和精力維護兩個組件。


          此外,當你使用類似 Bit 這樣的構建系統時(就像我一樣),只需將 useBlog 組件導入本地開發環境,然后再修改完成之后重新推送回 Bit Cloud。Bit Cloud 的構建服務器可以依托依賴樹將此修改傳遞給整個應用。因此如果只執行一些簡單修改,甚至不需要訪問整個應用。


          模式四:React Providers


          此模式的核心是解決組件狀態共享。我們都曾是 props 下鉆式傳遞的受害者。但如果你還沒有經歷過,那這里簡單解釋下:“props 下鉆式傳遞”就是當你在組件樹中進行 props 傳遞時,這些 props 只會在最底層組件中被使用,而中間層的組件都不會使用該 props。例如,看看下面這張圖:

          props 下鉆式傳遞


          從 BlogListComponent 一直向下傳遞一個 isLoading 的 props 到 Loader。但是,isLoading 只在 Loader 組件中使用。因此,在這種情況下,組件不但會引入不必要的 props,還會有性能開銷。因為當 isLoading 發生變化時,即使組件沒有使用它,React 依然會重新渲染你的組件樹。


          因此,解決方案之一就是通過利用 React Context 來使用 React Context Provider 模式。React Context 是一組組件的狀態管理器,通過它,你可以為一組組件創建特定的上下文。通過這種方式,你可以在上下文中定義和管理狀態,讓不同層級的組件都可以直接訪問上下文,并按需使用 props。這樣就可以避免 props 下鉆式傳遞了。


          主題組件就是該模式的一個常見場景。例如,你需要在應用程序中全局訪問主題。但將主題傳遞到應用中的每個組件并不現實。你可以創建一個包含主題信息的 Context,然后通過 Context 來設置主題。看一下我是如何通過React Context實現主題的,以便更好地理解這一點:

          https://bit.cloud/lakinduhewa/react-design-patterns/contexts/consumer-component


          import { useContext, createContext } from 'react';
          export type SampleContextContextType = {
            /**
             * primary color of theme.
             */
            color?: string;
          };
          export const SampleContextContext = createContext<SampleContextContextType>({
            color: 'aqua'
          });
          export const useSampleContext = () => useContext(SampleContextContext);

          復制代碼


          在 Context 中定義了一種主題顏色,它將在所有實現中使用該顏色來設置字體顏色。接下來,我還導出了一個 hook——useSampleContext,該 hook 讓消費者可以直接使用 Context。


          只是這樣還不行,我們還需要定義一個 Provider。Provider 是回答 "我應該與哪些組件共享狀態?"問題的組件。Provider的實現示例如下:


          import React, { ReactNode } from 'react';
          import { SampleContextContext } from './sample-context-context';
          export type SampleContextProviderProps = {
            /**
             * primary color of theme.
             */
            color?: string,
            /**
             * children to be rendered within this theme.
             */
            children: ReactNode
          };
          export function SampleContextProvider({ color, children }: SampleContextProviderProps) {
            return <SampleContextContext.Provider value={{ color }}>{children}</SampleContextContext.Provider>
          }

          復制代碼


          Provider 在管理初始狀態和設置 Context 可訪問狀態的組件方面起著至關重要的作用。


          接下來,你可以創建一個消費者組件來使用狀態:



          消費者組件


          模式五:條件渲染


          最后一個想和大家分享的是條件渲染模式。今天,人人都知道 React 中的條件渲染。它通過條件判斷來選擇組件進行渲染。


          但在實際使用中我們的用法常常是錯誤的:


          // ComponentA.js
          const ComponentA = () => {
              return <div>This is Component A</div>;
          };
          // ComponentB.js
          const ComponentB = () => {
              return <div>This is Component B</div>;
          };
          // ConditionalComponent.js
          import React, { useState } from 'react';
          import ComponentA from './ComponentA';
          import ComponentB from './ComponentB';
          const ConditionalComponent = () => {
              const [toggle, setToggle] = useState(true);
              return (
                  <div>
                      <button onClick={() => setToggle(!toggle)}>Toggle Component</button>
                      {toggle ? <ComponentA /> : <ComponentB />}
                  </div>
              );
          };
          export default ConditionalComponent;

          復制代碼


          你是否注意到,這里我們將基于條件的邏輯耦合到了 JSX 代碼片段中。通常,你不應該在 JSX 代碼中中添加任何與計算相關的邏輯,而只將與 UI 渲染相關的內容放在其中。


          解決這個問題的方法之一是使用條件渲染組件模式。創建一個可重用的 React 組件,該組件可以根據條件渲染兩個不同的組件。它的實現過程如下:


          import React, { ReactNode } from 'react';
          export type ConditionalProps = {
            /**
             * the condition to test against
             */
            condition: boolean
            /**
             * the component to render when condition is true
             */
            whenTrue: ReactNode
            /**
             * the component to render when condition is false
             */
            whenFalse: ReactNode
          };
          export function Conditional({ condition, whenFalse, whenTrue }: ConditionalProps) {
            return condition ? whenTrue : whenFalse;
          }

          復制代碼


          我們創建了一個可以按條件渲染兩個組件的組件。當我們將其集成到其他組件中時,會使代碼更簡潔,因為無需在 React 組件中加入復雜的渲染邏輯。你可以像下面這樣使用它:


          export const ConditionalTrue = () => {
            return (
              <Conditional
                condition
                whenFalse="You're False"
                whenTrue="You're True"
              />
            );
          }
          export const ConditionalFalse = () => {
            return (
              <Conditional
                condition={false}
                whenFalse="You're False"
                whenTrue="You're True"
              />
            );
          }

          復制代碼


          實際的輸入如下:



          總結


          掌握這五種設計模式,為 2024 年做好充分準備,構建出可擴展和可維護的應用吧。


          如果你想詳細深入本文中討論的模式,請隨時查看我在Bit Cloud的空間:

          https://bit.cloud/lakinduhewa/react-design-patterns


          感謝你的閱讀!

          原文鏈接:2024年,你應該知道的5種React設計模式_架構/框架_InfoQ精選文章

          自:coderwhy


          前面說過,整個前端已經是組件化的天下,而CSS的設計就不是為組件化而生的,所以在目前組件化的框架中都在需要一種合適的CSS解決方案。

          一. React中的css方案

          1.1. react中的css

          事實上,css一直是React的痛點,也是被很多開發者吐槽、詬病的一個點。

          在組件化中選擇合適的CSS解決方案應該符合以下條件:

          • 可以編寫局部css:css具備自己的具備作用域,不會隨意污染其他組件內的原生;
          • 可以編寫動態的css:可以獲取當前組件的一些狀態,根據狀態的變化生成不同的css樣式;
          • 支持所有的css特性:偽類、動畫、媒體查詢等;
          • 編寫起來簡潔方便、最好符合一貫的css風格特點;
          • 等等...

          在這一點上,Vue做的要遠遠好于React:

          • Vue通過在.vue文件中編寫 <style><style> 標簽來編寫自己的樣式;
          • 通過是否添加 scoped 屬性來決定編寫的樣式是全局有效還是局部有效;
          • 通過 lang 屬性來設置你喜歡的 less、sass等預處理器;
          • 通過內聯樣式風格的方式來根據最新狀態設置和改變css;
          • 等等...

          Vue在CSS上雖然不能稱之為完美,但是已經足夠簡潔、自然、方便了,至少統一的樣式風格不會出現多個開發人員、多個項目采用不一樣的樣式風格。

          相比而言,React官方并沒有給出在React中統一的樣式風格:

          • 由此,從普通的css,到css modules,再到css in js,有幾十種不同的解決方案,上百個不同的庫;
          • 大家一直在尋找最好的或者說最適合自己的CSS方案,但是到目前為止也沒有統一的方案;

          在這篇文章中,我會介紹挑選四種解決方案來介紹:

          • 方案一:內聯樣式的寫法;
          • 方案二:普通的css寫法;
          • 方案三:css modules;
          • 方案四:css in js(styled-components);

          1.2. 普通的解決方案

          1.2.1. 內聯樣式

          內聯樣式是官方推薦的一種css樣式的寫法:

          • style 接受一個采用小駝峰命名屬性的 JavaScript 對象,,而不是 CSS 字符串;
          • 并且可以引用state中的狀態來設置相關的樣式;
          export default class App extends PureComponent {
            constructor(props) {
              super(props);
          
              this.state = {
                titleColor: "red"
              }
            }
          
            render() {
              return (
                <div>
                  <h2 style={{color: this.state.titleColor, fontSize: "20px"}}>我是App標題</h2>
                  <p style={{color: "green", textDecoration: "underline"}}>我是一段文字描述</p>
                </div>
              )
            }
          }

          內聯樣式的優點:

          • 1.內聯樣式, 樣式之間不會有沖突
          • 2.可以動態獲取當前state中的狀態

          內聯樣式的缺點:

          • 1.寫法上都需要使用駝峰標識
          • 2.某些樣式沒有提示
          • 3.大量的樣式, 代碼混亂
          • 4.某些樣式無法編寫(比如偽類/偽元素)

          所以官方依然是希望內聯合適和普通的css來結合編寫;

          1.2.2. 普通的css

          普通的css我們通常會編寫到一個單獨的文件。

          App.js中編寫React邏輯代碼:

          import React, { PureComponent } from 'react';
          
          import Home from './Home';
          
          import './App.css';
          
          export default class App extends PureComponent {
            render() {
              return (
                <div className="app">
                  <h2 className="title">我是App的標題</h2>
                  <p className="desc">我是App中的一段文字描述</p>
                  <Home/>
                </div>
              )
            }
          }

          App.css中編寫React樣式代碼:

          .title {
            color: red;
            font-size: 20px;
          }
          
          .desc {
            color: green;
            text-decoration: underline;
          }

          這樣的編寫方式和普通的網頁開發中編寫方式是一致的:

          • 如果我們按照普通的網頁標準去編寫,那么也不會有太大的問題;
          • 但是組件化開發中我們總是希望組件是一個獨立的模塊,即便是樣式也只是在自己內部生效,不會相互影響;
          • 但是普通的css都屬于全局的css,樣式之間會相互影響;

          比如編寫Home.js的邏輯代碼:

          import React, { PureComponent } from 'react';
          
          import './Home.css';
          
          export default class Home extends PureComponent {
            render() {
              return (
                <div className="home">
                  <h2 className="title">我是Home標題</h2>
                  <span className="desc">我是Home中的span段落</span>
                </div>
              )
            }
          }

          又編寫了Home.css的樣式代碼:

          .title {
            color: orange;
          }
          
          .desc {
            color: purple;
          }

          最終樣式之間會相互層疊,只有一個樣式會生效;

          1.2.3. css modules

          css modules并不是React特有的解決方案,而是所有使用了類似于webpack配置的環境下都可以使用的。

          但是,如果在其他項目中使用,那么我們需要自己來進行配置,比如配置webpack.config.js中的modules: true等。

          但是React的腳手架已經內置了css modules的配置:

          • .css/.less/.scss 等樣式文件都修改成 .module.css/.module.less/.module.scss 等;
          • 之后就可以引用并且進行使用了;

          使用的方式如下:

          css modules用法

          這種css使用方式最終生成的class名稱會全局唯一:

          生成的代碼結構

          css modules確實解決了局部作用域的問題,也是很多人喜歡在React中使用的一種方案。

          但是這種方案也有自己的缺陷:

          • 引用的類名,不能使用連接符(.home-title),在JavaScript中是不識別的;
          • 所有的className都必須使用{style.className} 的形式來編寫;
          • 不方便動態來修改某些樣式,依然需要使用內聯樣式的方式;

          如果你覺得上面的缺陷還算OK,那么你在開發中完全可以選擇使用css modules來編寫,并且也是在React中很受歡迎的一種方式。

          二. CSS in JS

          2.1. 認識CSS in JS

          實際上,官方文檔也有提到過CSS in JS這種方案:

          • “CSS-in-JS” 是指一種模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定義;
          • 注意此功能并不是 React 的一部分,而是由第三方庫提供。 React 對樣式如何定義并沒有明確態度;

          在傳統的前端開發中,我們通常會將結構(HTML)、樣式(CSS)、邏輯(JavaScript)進行分離。

          • 但是在前面的學習中,我們就提到過,React的思想中認為邏輯本身和UI是無法分離的,所以才會有了JSX的語法。
          • 樣式呢?樣式也是屬于UI的一部分;
          • 事實上CSS-in-JS的模式就是一種將樣式(CSS)也寫入到JavaScript中的方式,并且可以方便的使用JavaScript的狀態;
          • 所以React有被人稱之為 All in JS;

          當然,這種開發的方式也受到了很多的批評:

          • Stop using CSS in JavaScript for web development
          • https://hackernoon.com/stop-using-css-in-javascript-for-web-development-fa32fb873dcc

          批評聲音雖然有,但是在我們看來很多優秀的CSS-in-JS的庫依然非常強大、方便:

          • CSS-in-JS通過JavaScript來為CSS賦予一些能力,包括類似于CSS預處理器一樣的樣式嵌套、函數定義、邏輯復用、動態修改狀態等等;
          • 依然CSS預處理器也具備某些能力,但是獲取動態狀態依然是一個不好處理的點;
          • 所以,目前可以說CSS-in-JS是React編寫CSS最為受歡迎的一種解決方案;

          目前比較流行的CSS-in-JS的庫有哪些呢?

          • styled-components
          • emotion
          • glamorous

          目前可以說styled-components依然是社區最流行的CSS-in-JS庫,所以我們以styled-components的講解為主;

          安裝styled-components:

          yarn add styled-components

          2.2. styled-components

          2.2.1. 標簽模板字符串

          ES6中增加了模板字符串的語法,這個對于很多人來說都會使用。

          但是模板字符串還有另外一種用法:標簽模板字符串(Tagged Template Literals)。

          我們一起來看一個普通的JavaScript的函數:

          function foo(...args) {
            console.log(args);
          }
          
          foo("Hello World");

          正常情況下,我們都是通過 函數名() 方式來進行調用的,其實函數還有另外一種調用方式:

          foo`Hello World`; // [["Hello World"]]

          如果我們在調用的時候插入其他的變量:

          • 模板字符串被拆分了;
          • 第一個元素是數組,是被模塊字符串拆分的字符串組合;
          • 后面的元素是一個個模塊字符串傳入的內容;
          foo`Hello ${name}`; // [["Hello ", ""], "kobe"];

          在styled component中,就是通過這種方式來解析模塊字符串,最終生成我們想要的樣式的

          2.2.2. styled基本使用

          styled-components的本質是通過函數的調用,最終創建出一個組件:

          • 這個組件會被自動添加上一個不重復的class;
          • styled-components會給該class添加相關的樣式;

          比如我們正常開發出來的Home組件是這樣的格式:

          <div>
            <h2>我是Home標題</h2>
            <ul>
              <li>我是列表1</li>
              <li>我是列表2</li>
              <li>我是列表3</li>
            </ul>
          </div>

          我們希望給外層的div添加一個特殊的class,并且添加相關的樣式:

          styled-components基本使用

          另外,它支持類似于CSS預處理器一樣的樣式嵌套:

          • 支持直接子代選擇器或后代選擇器,并且直接編寫樣式;
          • 可以通過&符號獲取當前元素;
          • 直接偽類選擇器、偽元素等;
          const HomeWrapper = styled.div`
            color: purple;
          
            h2 {
              font-size: 50px;
            }
          
            ul > li {
              color: orange;
          
              &.active {
                color: red;
              }
          
              &:hover {
                background: #aaa;
              }
          
              &::after {
                content: "abc"
              }
            }
          `

          最終效果如下

          2.2.3. props、attrs屬性

          props可以穿透

          定義一個styled組件:

          const HYInput = styled.input`
            border-color: red;
          
            &:focus {
              outline-color: orange;
            }
          `

          使用styled的組件:

          <HYInput type="password"/>

          props可以被傳遞給styled組件

          <HomeWrapper color="blue">
          </HomeWrapper>

          使用時可以獲取到傳入的color:

          • 獲取props需要通過${}傳入一個插值函數,props會作為該函數的參數;
          • 這種方式可以有效的解決動態樣式的問題;
          const HomeWrapper = styled.div`
            color: ${props => props.color};
          }

          添加attrs屬性

          const HYInput = styled.input.attrs({
            placeholder: "請填寫密碼",
            paddingLeft: props => props.left || "5px"
          })`
            border-color: red;
            padding-left: ${props => props.paddingLeft};
          
            &:focus {
              outline-color: orange;
            }
          `

          2.2.4. styled高級特性

          支持樣式的繼承

          編寫styled組件

          const HYButton = styled.button`
            padding: 8px 30px;
            border-radius: 5px;
          `
          
          const HYWarnButton = styled(HYButton)`
            background-color: red;
            color: #fff;
          `
          
          const HYPrimaryButton = styled(HYButton)`
            background-color: green;
            color: #fff;
          `

          按鈕的使用

          <HYButton>我是普通按鈕</HYButton>
          <HYWarnButton>我是警告按鈕</HYWarnButton>
          <HYPrimaryButton>我是主要按鈕</HYPrimaryButton>

          styled設置主題

          在全局定制自己的主題,通過Provider進行共享:

          import { ThemeProvider } from 'styled-components';
          
          <ThemeProvider theme={{color: "red", fontSize: "30px"}}>
            <Home />
            <Profile />
          </ThemeProvider>

          在styled組件中可以獲取到主題的內容:

          const ProfileWrapper = styled.div`
            color: ${props => props.theme.color};
            font-size: ${props => props.theme.fontSize};
          `

          2.3. classnames

          vue中添加class

          在vue中給一個元素添加動態的class是一件非常簡單的事情:

          你可以通過傳入一個對象:

          <div
            class="static"
            v-bind:class="{ active: isActive, 'text-danger': hasError }"
          ></div>

          你也可以傳入一個數組:

          <div v-bind:class="[activeClass, errorClass]"></div>

          甚至是對象和數組混合使用:

          <div v-bind:class="[{ active: isActive }, errorClass]"></div>

          react中添加class

          React在JSX給了我們開發者足夠多的靈活性,你可以像編寫JavaScript代碼一樣,通過一些邏輯來決定是否添加某些class:

          import React, { PureComponent } from 'react'
          
          export default class App extends PureComponent {
            constructor(props) {
              super(props);
          
              this.state = {
                isActive: true
              }
            }
          
            render() {
              const {isActive} = this.state; 
          
              return (
                <div>
                  <h2 className={"title " + (isActive ? "active": "")}>我是標題</h2>
                  <h2 className={["title", (isActive ? "active": "")].join(" ")}>我是標題</h2>
                </div>
              )
            }
          }
          

          這個時候我們可以借助于一個第三方的庫:classnames

          • 很明顯,這是一個用于動態添加classnames的一個庫。

          我們來使用一下最常見的使用案例:

          classNames('foo', 'bar'); // => 'foo bar'
          classNames('foo', { bar: true }); // => 'foo bar'
          classNames({ 'foo-bar': true }); // => 'foo-bar'
          classNames({ 'foo-bar': false }); // => ''
          classNames({ foo: true }, { bar: true }); // => 'foo bar'
          classNames({ foo: true, bar: true }); // => 'foo bar'
          
          // lots of arguments of various types
          classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
          
          // other falsy values are just ignored
          classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'

          我是@半糖學前端 ,專注前端技術領域分享,關注我和我一起學習,共同進步!

          今,縱觀各類招聘網站上的前端職位,大家往往都會看到一個熟悉的字眼:React。雖然企業雇主也經常會列出其他一些類似的前端框架選項,但 React 的地位幾乎是雷打不動。


          但面對這樣的現實,請原諒我始終無法理解。除了流行,React 到底還有什么優勢?


          首先我想澄清一點,我對 React 沒有任何敵意。我知道它挺好,而且如果需要開發龐大復雜的前端項目,我其實也并不抗拒使用 React。


          React 的出現,為其他框架當前及未來的功能規劃奠定了基礎。Vue 3及其組合 API 明顯是受到 React hooks 的啟發。Svelte 中的很多約定就來自 React。至于我最喜愛的 Vue 框架 Nuxt,它的大部分設計思路都來自 React 的元框架 Next。總而言之,整個基于組件的前端設計和開發模型,特別是目前的整個生態系統,都離不開 React 的鼎力支持。


          但就個人觀點,我還是覺得 React 像是那種開創了流派的祖師級老電影、音樂專輯或者電子游戲。它的偉大體現在那個時間點上的開創性,但時至今日自身的絕對價值其實已經很有限了。


          好吧,這話可能說得有點狠。畢竟一提到開創性的老電影,大家首先想到的就是《公民凱恩》,React 跟其他框架間的差距肯定不像《公民凱恩》相較于后來的經典佳作那么大。我的觀點很簡單:


          React 已經老了,只是經常用它的朋友們還沒有意識到它老到了什么程度、引發了哪些問題。

          如果只用 React,那可能會覺得這框架不錯啊,而且一直在努力改進。確實,React 在很多方面都是越來越好,但這并不能改變它的發展速度和功能上限已經長期跟不上同類方案的事實。

          總之一句話,React 表現得不錯,只是不像其他框架那么好。


          工作中的最佳選項


          假設大家身為某家科技初創公司的 CTO,或者是打算開發某網絡軟件新產品的個人創業者。


          我們面前擺著新項目的藍圖,可以隨意選擇自己喜愛的技術進行構建。沒有約束,也不設產品生命周期限制,那么你會選擇哪一種前端框架?


          (有些朋友可能會抬杠說,不用前端框架也行。但對于任何成規模、比較復雜的項目來說,不用前端框架肯定不是什么好主意。)


          要做出選擇,先要考慮以下幾項條件:


          • 性能
          • 學習曲線
          • 綁定包大小
          • 可擴展性
          • 社區與支持
          • 資金支持
          • 開發者體驗
          • 人才供應


          而有沒有一種可能,從這么多角度來論證,其實 React 并不是什么好選擇。


          下面咱們逐一探討。


          性能


          大家可以通過多種不同指標來衡量性能。但無論關注哪個具體方面,React 都稱不上頂級水準。Vue、Svelte、Solid、Inferno 等工具的性能總體上都要好于 React。根據實際需求,大家甚至可以通過 Alpine 或者 Petite Vue 等讓性能更上一層樓(雖然我覺得這兩種跟其他框架并不算同一類方案)。


          React 的性能不佳應該已經是個共識,所以這里無需繼續贅述。所以我認為,如果大家希望讓新項目擁有強大的性能表現,那 React 就可以直接被排除在外。


          學習曲線


          假設你對前端框架一無所知,那 React 也絕對不是最好學、最容易上手的選項。


          JSX的實質,就是笨拙地把 HTML 硬塞進 JavaScript 函數返回。你以為這就夠糟了?不,更糟的是你在 React 里不用 JSX。


          除了 JSX,React 本身也有不少獨特的約束和毛病(比如提供兩種完全不同的語法,但二者完全無法互操作)。


          在 React 中,其他前端框架能夠幫我們輕松打理的小事,往往還是需要手動干預或者借助大量樣板文件(甚至二者兼有)。


          幾乎每種前端框架都把用戶設想成普通人,但 React 不同,它最早是專為 Facebook 的工程師們打造的。雖然經過多年發展,它已經成為一種比較通行的市場化產品,但即使到現在,這樣的出身仍然給 React 留下了深深的烙印。我們還是可以看到其中留下的早期決策與優化痕跡。


          至于其他問題,那可太多了。萬惡之源 useEffect,不能在 JSX 中使用某些標準 HTML 屬性(因為 JSX 無法區分 React prop 和 HTML 屬性),記憶化,只能靠短路運算符模仿出來的虛假條件,以及要求開發者自己防止無限循環等等……其實連這里的循環都是假的,必須靠數組方法才能實現。不說了,說不完。


          綁定包大小


          這一點跟速度類似,但我覺得還是有必要區分開來。哪怕下載包大一點,但實際使用時性能更好,那也沒啥問題。但 React 可不是這樣。


          React 軟件包相當臃腫,這個大家都知道了,我也不多廢話。


          我想強調的是,有些人覺得 React 大多數情況下會從緩存中加載,所以綁定包大小無所謂。這種認知最早是假的,后來現代瀏覽器讓它成了真,可最近的安全升級開始阻止域之間的緩存共享,所以又成了假的。


          另外,Preact 雖然表現不錯,但還沒辦法跟 React 無縫對接。而且 Preact 的包大小跟其他前端框架比也沒有太大的優勢。


          可擴展性


          雖然 React 對應的企業應用規模肯定是最大的,但我覺得吧,數量跟質量并不是一回事。


          從 Vue 到Svelte,再到Angular和 Ember,每一款主流前端框架都擁有類似的大規模執行能力。他們的網站主頁上,也不乏一個個聲名顯赫的重量級客戶徽標。


          所以 React 沒什么特別的,只是案例最多罷了。


          如果大家有很強的從眾心理,那我也無話可說。但客戶多真的不代表 React 就一定更優秀,它只是出現在了充滿機會的時代。


          社區與支持


          沒錯,React 背后的社區規模最大,但這還是不足以支撐 React 就最好的結論。


          大社區也有負面影響,特別是對 React 這類“無傾向性”框架而言。社區過大可能對應著太多可供選擇的套餐,太多相互沖突、彼此對抗的觀點,逼著用戶在其中站隊,然后承受隨之而來的一切。


          不僅如此,社區太大,甚至不一定能讓產品越變越好。


          最初,更多的參與者確實能不斷帶來好的功能特性。但這里存在一個收益遞減點(即不斷上升的溝通成本,逐漸開始減慢、而非加快項目發展速度)。除此之外,社區的參與人數和社區質量間也沒有必然關聯。


          當然,我理解想要去爆滿的餐廳吃飯那種心情,這似乎能給人一種安全感。畢竟哪怕不好吃,還有那么多人跟我一起上當呢,這波不虧。但哪怕人再多,都無法改變“不好吃”這個基本事實。這跟愿意來吃的人是多是少,真的沒什么關系。所以我們用不著非往最火爆的餐廳去擠,挑一家適合自己的、安靜享受一頓美食,不就足夠了?


          資金支持


          有些人總擔心自己使用的框架,會在某一天突然消失,由此失去支持和維護。對他們來說,出自 Facebook 系的 React 天然值得信任。但問題是,其他前端項目的資金支持有那么不堪嗎?


          Angular 的背后可是谷歌,Vue 則是歷史上最成功、資金也最充裕的開源項目之一。Vercel 目前至少雇用了兩位 Svelte 維護者(其中包括 Svelte 的締造者)全職負責項目管理。至于 Solid,已經擁有超過 100 名貢獻者和至少六家主要企業贊助商。


          所以,資金支持對各大主流前端框架來說都不是問題,React 在這方面同樣不算占優。


          開發者體驗


          React 確實是應用范圍最廣的前端框架,知名度也是一時無兩。但在今年的 JS 現狀調查報告中,React 的開發者滿意度已經不及 Solid 和 Svelte。至于受關注度,React 落后于 Svelte、Solid 和 Vue,甚至已經跌破 50%。


          多年以來,React 的用戶滿意度和關注度一直在穩步下降,采用率也停滯不前。


          當然,這類調查問卷只能作為參考,在問題的表述方式上稍做手腳就能得出不同的答案。但其他同類調查也發現了類似的趨勢。在 Stack Overflow 的調查中,React 的受歡迎度遠低于 Svelte,僅略微高于 Vue。


          有趣的是,Scott Tolinski 最近提到,他的一位開發者放棄了薪酬豐厚的 React 職位,寧愿拿一半的工資也要加入 Tolinski 領導的 SvelteKit 項目。


          當然了,并不能用這個例子證明開發者連錢都愿意放棄,就為了離 React 遠一點。但至少能夠看出,React 帶給開發者的使用感受實在稱不上好。


          人才供應


          這方面,React 確實堪稱一騎絕塵。如果想讓新人快速理解之前的開發資產,那 React 的優勢可太明顯了。


          但是吧,我覺得單這一點不足以讓 React 脫穎而出。


          鑒于選擇 React 之后,應用程序的綁定包本身就更大、速度更慢而且復雜性更高,用這么多弊端來換取所謂項目接管期間的一點點便利,無疑是在犧牲長遠收益尋求眼下省事。翻譯翻譯,這不就是典型的技術債務嗎?


          現在省下的幾個禮拜,未來可能需要幾個月甚至幾年來償還。所以雖然 React 在這方面占優,但大家最好還是認真核算一下,沒辦法無腦選它。


          另外,了解 React 的朋友想上手其他前端框架,應該不是什么難事。沒錯,不同框架間總有一些細微差別和小怪癖,但其中遵循的設計理念還是大體相同的。任何熟悉 React 的優秀開發者,也都能在其他框架上獲得同樣的工作成效。


          我承認,商業世界從來沒有好壞,只有權衡取舍。開發速度很重要,前面提到的每一點也都很重要。所以,您的實際情況可能證明,哪怕是 React 速度更慢、綁定包更大、復雜度更高,它也仍然值得選擇。是的,我都理解。


          但大家做出的選擇,最終也將成為與競爭對手在市場上搏殺時的一張牌。選得好就是好牌,反之亦然。如果大多數對手也選擇 React,那大家就是爛牌對局、菜雞互啄。


          而如果能選得更好,也許就能壓制對方的牌形。


          批評了半天,React 為什么還是傲視同儕?


          因為很多人在做選擇時,往往是比較草率的。React 之所以現在受歡迎,就是因為 React 之前受歡迎。


          在真正占領市場之前,人們其實是出于其他理由去選擇 React 的。也許因為它能解決開發者面對的實際問題,也許因為它比較新奇有趣,或者是其他什么原因。但可以肯定的是,當時人們選 React 絕不是看中它更好就業,或者是市場普及率最高的框架。而時過境遷,現在大家再選擇它,唯一的理由就是它夠老、夠踏實。


          企業選它,因為人才市場上懂 React 的群體很大;求職者學它,是因為人才市場上企業想要招聘 React 開發者。這是個自我強化的循環,一個自我實現的預言。


          于是 React 成了默認選項,只要沒有充分的理由,更多人只會以無所謂的態度接著用。


          while (reactIsPopular) {
            reactIsPopular = true
          }

          復制代碼


          “畢竟沒人會因為用了 React 而被解雇”,這話倒也沒錯。React 是個安全的選擇,可能不是每個人的最愛,但至少不會惹出大麻煩。它,還是能干活的。


          所以只要沒有強烈的業務需求,求職者和招聘方都可以接受圍繞 React 建立起來的行業現狀。只要能把雙方對接起來,React 的作用就已經達到了。


          這一切會改變嗎?如何改變?


          我其實一直在關注事態的變化。


          但要說答案,我也沒有。根據之前提到的幾份調查報告,React 的采用率確實出現了停滯。也不能說不再增長,只能說 React 的增長跟不斷擴大的市場本體之間保持了同步,三年來份額一直維持在 80%左右。


          但終有一天,我相信這種循環會中斷。但我不知道具體的導火索會是什么。


          也許是個量變引發質變的過程。回想起來,很多趨勢來得看似突然,但實際上一直在隨時間推移而積蓄力量。也許其他前端框架更好地證明了自己,并逐漸削平了 React 在人才儲備方面的優勢,于是企業開始向其他方案敞開懷抱。


          也可能會有部分企業觸及 React 的性能上限,并結合業務需求轉向性能更強的選項。比方說,如果公司的業務對移動端性能有著極高要求,而且必須能夠在設備配置差、網絡不穩定的區域內提供良好體驗,那 React 差出的這部分性能就足以促成改變。


          但對大多數企業來說,情況還沒那么極端。大部分舊項目實在沒必要做遷移,性能困擾雖然偶然出現,也絕不至于要因此推動大規模重構。所以繼續用 React 完全沒有問題,它在很多場景下已經完全夠用了。


          所以沒準市場就固化在了這一刻,再沒有誰能真正挑戰 React 的統治地位。等到它真正宣布退位的時候,也許我們已經徹底拋棄了前端框架,那時候主流瀏覽器、特別是 JS 已經擴展到了不需要它們的地步。這就是所謂后框架時代。


          還有一種可能,React 在事實上已經過時了,只是在宏觀統計上還體現不出來。目前人才市場上的招聘需求,反映的是企業在很久之前做出的框架選擇。正如核酸測試體現的是幾天、甚至幾周之前的區域內疫情狀況一樣,目前的招聘態勢也許也存在滯后。


          我不知道未來的前端會是什么樣子,應該沒人能做出準確的預言。但可以肯定的是,React 還能風光上好一陣子。


          如果大家正在學習前端開發,想用這個為自己找份工作或提升職業水平,那 React 是個不錯的選項、也是非常安全的方向。


          但我也希望能有越來越多的開發者積極探索其他選項,也希望企業能給他們更多嘗試的機會。近年來,前端開發領域的驚喜都來自 Vue 和 Svelte。以我個人的感受和經驗,React 只是能干活,并沒讓工作變得更有趣。


          而且越來越多的開發者也意識到了這個問題,開始嘗試接觸其他框架、體驗它們的不同特性,也反過來意識到 React 是有多么老邁和遲鈍。即使單純從推動未來前端開發者多樣性的角度出發,我也真心建議大家用用別的框架方案。


          原文鏈接:


          https://joshcollinsworth.com/blog/self-fulfilling-prophecy-of-react


          主站蜘蛛池模板: 亚洲色精品三区二区一区| 一区二区三区视频观看| 欧美av色香蕉一区二区蜜桃小说| 国产精品美女一区二区视频| 日韩伦理一区二区| 成人区人妻精品一区二区不卡视频 | 高清一区二区在线观看| 国产成人无码一区二区三区| 韩国精品一区二区三区无码视频| 国产成人精品亚洲一区| 亚洲av无码一区二区三区天堂古代| 久久久久国产一区二区三区| 国产成人av一区二区三区不卡 | 精品性影院一区二区三区内射| 国产一区二区在线观看麻豆| 曰韩人妻无码一区二区三区综合部 | 色婷婷综合久久久久中文一区二区| 亚洲AV无码一区二区三区电影| 香蕉一区二区三区观| 视频一区二区在线观看| 国产主播一区二区三区| 国产成人欧美一区二区三区| 久久一区二区三区免费播放| 久久精品国产一区| 久久国产免费一区二区三区| 精品无码人妻一区二区三区| 亚洲高清日韩精品第一区| 亚洲中文字幕无码一区| 国产主播一区二区三区在线观看| 亚洲欧洲精品一区二区三区| eeuss鲁片一区二区三区| 在线精品国产一区二区三区| 农村乱人伦一区二区| 亚洲av成人一区二区三区在线观看| 亚洲综合无码AV一区二区| 色综合久久一区二区三区| 国产一区二区三区久久精品| 精品亚洲福利一区二区| 青青青国产精品一区二区| 久久AAAA片一区二区| 日韩在线视频一区二区三区|