Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
者 | Lakindu Hewawasam
譯者 | 許學(xué)文
策劃 | 丁曉昀
如果你在開發(fā)工作中使用的是 React 框架,那么首當(dāng)其沖要學(xué)習(xí)的就是思考如何設(shè)計(jì)組件。組件設(shè)計(jì)并非簡單地將多個(gè)組件合成一個(gè)集合,而是需要思考如何設(shè)計(jì)更小、復(fù)用性更強(qiáng)的組件。例如,思考下面這張組件圖:
簡化的組件圖
圖中有三個(gè)組件,分別是:
如圖所示,Typography 組件同時(shí)被 Footer 和 Sizeable Box 組件使用。通常我們以為這樣,就能構(gòu)建一個(gè)簡單、易維護(hù)和易排除錯(cuò)誤的應(yīng)用了。但其實(shí)只是這樣思考組件的設(shè)計(jì)是遠(yuǎn)遠(yuǎn)不夠的。
如果你知道如何從組件的視角思考問題,就可以通過在 React 組件中使用設(shè)計(jì)模式來提高代碼的整體模塊性、可擴(kuò)展性和可維護(hù)性。
因此,下面這五種設(shè)計(jì)模式,是你在使用 React 時(shí)必須要掌握的。
首先,在使用 React 時(shí)候,請嘗試為應(yīng)用設(shè)計(jì)基礎(chǔ)組件。
基礎(chǔ)UI組件,就是一個(gè)具備默認(rèn)行為且支持定制化的組件。
例如,每個(gè)應(yīng)用都會通過基礎(chǔ)的樣式、按鈕設(shè)計(jì)或者基礎(chǔ)的排版,來實(shí)現(xiàn)應(yīng)用在視覺和交互上的一致性。這些組件的設(shè)計(jì)特點(diǎn)是:
通過一個(gè) Button 組件就能很好地說明基礎(chǔ)組件模式的實(shí)現(xiàn)。示例如下:
現(xiàn)在你就可以利用基礎(chǔ)組件模式進(jìn)行設(shè)計(jì),使組件的使用者可以改變其行為。請參考我基于基礎(chǔ)組件模式完成的Button組件,示例代碼如下:
import React, { ButtonHTMLAttributes } from 'react';
// 按鈕組件的形態(tài):實(shí)心或者空心
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>
);
}
復(fù)制代碼
仔細(xì)觀察代碼會發(fā)現(xiàn),這里 Button 組件的 props 類型合并了原生 HTML 中 button 標(biāo)簽屬性的全部類型。這意味著,使用者除了可以為 Button 組件設(shè)置默認(rèn)配置外,還可以設(shè)置諸如 onClick、aria-label 等自定義配置。這些自定義配置會通過擴(kuò)展運(yùn)算符傳遞給 Button 組件內(nèi)部的 button 標(biāo)簽。
通過不同的上下文設(shè)置,可以看到不同的 Button 組件的形態(tài),效果截圖如下圖。
這個(gè)可以查看具體設(shè)置:
https://bit.cloud/lakinduhewa/react-design-patterns/base/button/~compositions
基礎(chǔ)組件在不同上下文中的使用效果
通過不同的上下文,你可以設(shè)定組件的行為。這可以讓組件成為更大組件的基礎(chǔ)。
在成功創(chuàng)建了基礎(chǔ)組件后,你可能會希望基于基礎(chǔ)組件創(chuàng)建一些新的組件。
例如,你可以使用之前創(chuàng)建的 Button 組件來實(shí)現(xiàn)一個(gè)標(biāo)準(zhǔn)的 DeleteButton 組件。通過在應(yīng)用中使用該 DeleteButton,可以讓應(yīng)用中所有刪除操作在顏色、形態(tài)以及字體上保持一致。
不過,如果出現(xiàn)重復(fù)組合一組組件來實(shí)現(xiàn)相同效果的現(xiàn)象,那么你可以考慮將它們封裝到一個(gè)組件中。
下面,讓我們來看看其中一種實(shí)現(xiàn)方案:
https://bit.cloud/lakinduhewa/react-design-patterns/composition/delete-button
使用組合模式創(chuàng)建組件
如上面的組件依賴圖所示,DeleteButton 組件使用基礎(chǔ)的 Button 組件為所有與刪除相關(guān)的操作提供標(biāo)準(zhǔn)的實(shí)現(xiàn)。下面是基本代碼實(shí)現(xiàn):
// 這里引入了,基礎(chǔ)按鈕組件和其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>
);
}
復(fù)制代碼
我們使用基于模式一創(chuàng)建的 Button 組件來實(shí)現(xiàn)的 DeleteButton 組件的效果如下:
現(xiàn)在我們可以在應(yīng)用中使用統(tǒng)一的刪除按鈕。此外,如果你使用類似 Bit 的構(gòu)建系統(tǒng)進(jìn)行組件的設(shè)計(jì)和構(gòu)建,那么當(dāng) Button 組件發(fā)生改變時(shí),可以讓CI服務(wù)自動將此改變傳遞到DeleteButton組件上,就像下面這樣(當(dāng) Button 組件從 0.0.3 升級到了 0.0.4,那么 CI 服務(wù)會自動觸發(fā),將 DeleteButton 組件從 0.0.1 升級到 0.0.2):
Bit 上的一個(gè) CI 構(gòu)建
React Hooks 是React v16就推出來的特性,它不依賴類組件實(shí)現(xiàn)狀態(tài)管理、負(fù)效應(yīng)等概念。簡而言之,就是你可以通過利用 Hooks API 擺脫對類組件的使用需求。useSate 和 useEffect 是最廣為人知的兩個(gè) Hooks API,但本文不打算討論它們,我想重點(diǎn)討論如何利用 Hooks 來提高組件的整體可維護(hù)性。
例如,請考慮下面這個(gè)場景:
基于上面的案例,你可能會像下面這樣將 API 邏輯直接寫在函數(shù)組件中:
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;
復(fù)制代碼
這樣寫,組件也能正常工作。它將會獲取博客文章列表并且渲染在 UI 上。但是,這里將 UI 邏輯和 API 邏輯混在一起了。
理想情況下,React 組件應(yīng)該不需要關(guān)系如何獲取數(shù)據(jù)。而只需要關(guān)心接收一個(gè)數(shù)據(jù)數(shù)組,然后將其呈現(xiàn)在 DOM 上。
因此,實(shí)現(xiàn)這一目標(biāo)的最佳方法是將 API 邏輯抽象到 React Hook 中,以便在組件內(nèi)部進(jìn)行調(diào)用。這樣做就可以打破 API 調(diào)用與組件之間的耦合。通過這種方式,就可以在不影響組件的情況下,修改底層的數(shù)據(jù)獲取邏輯。
其中一種實(shí)現(xiàn)方式如下。
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非實(shí)際需要,只是為了模擬API調(diào)用
setTimeout(() => {
setBlogs(Blogs);
setLoading(false);
}, 3000);
}, []);
return { blogs, loading }
}
復(fù)制代碼
如上代碼所示,useBlog hook 獲取博客列表數(shù)據(jù),然后賦值給狀態(tài)變量,最后通過導(dǎo)出變量給到消費(fèi)者(BlogList 組件)使用:
Hook 效果
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>
);
}
復(fù)制代碼
BlogList 組件效果
通過調(diào)用 useBlog 和使用其導(dǎo)出的狀態(tài)變量,我們在 BlogList 組件中使用了 Hooks。如此,相對于之前,我們可以減少大量代碼,并以最少的代碼和精力維護(hù)兩個(gè)組件。
此外,當(dāng)你使用類似 Bit 這樣的構(gòu)建系統(tǒng)時(shí)(就像我一樣),只需將 useBlog 組件導(dǎo)入本地開發(fā)環(huán)境,然后再修改完成之后重新推送回 Bit Cloud。Bit Cloud 的構(gòu)建服務(wù)器可以依托依賴樹將此修改傳遞給整個(gè)應(yīng)用。因此如果只執(zhí)行一些簡單修改,甚至不需要訪問整個(gè)應(yīng)用。
此模式的核心是解決組件狀態(tài)共享。我們都曾是 props 下鉆式傳遞的受害者。但如果你還沒有經(jīng)歷過,那這里簡單解釋下:“props 下鉆式傳遞”就是當(dāng)你在組件樹中進(jìn)行 props 傳遞時(shí),這些 props 只會在最底層組件中被使用,而中間層的組件都不會使用該 props。例如,看看下面這張圖:
props 下鉆式傳遞
從 BlogListComponent 一直向下傳遞一個(gè) isLoading 的 props 到 Loader。但是,isLoading 只在 Loader 組件中使用。因此,在這種情況下,組件不但會引入不必要的 props,還會有性能開銷。因?yàn)楫?dāng) isLoading 發(fā)生變化時(shí),即使組件沒有使用它,React 依然會重新渲染你的組件樹。
因此,解決方案之一就是通過利用 React Context 來使用 React Context Provider 模式。React Context 是一組組件的狀態(tài)管理器,通過它,你可以為一組組件創(chuàng)建特定的上下文。通過這種方式,你可以在上下文中定義和管理狀態(tài),讓不同層級的組件都可以直接訪問上下文,并按需使用 props。這樣就可以避免 props 下鉆式傳遞了。
主題組件就是該模式的一個(gè)常見場景。例如,你需要在應(yīng)用程序中全局訪問主題。但將主題傳遞到應(yīng)用中的每個(gè)組件并不現(xiàn)實(shí)。你可以創(chuàng)建一個(gè)包含主題信息的 Context,然后通過 Context 來設(shè)置主題。看一下我是如何通過React Context實(shí)現(xiàn)主題的,以便更好地理解這一點(diǎn):
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);
復(fù)制代碼
在 Context 中定義了一種主題顏色,它將在所有實(shí)現(xiàn)中使用該顏色來設(shè)置字體顏色。接下來,我還導(dǎo)出了一個(gè) hook——useSampleContext,該 hook 讓消費(fèi)者可以直接使用 Context。
只是這樣還不行,我們還需要定義一個(gè) Provider。Provider 是回答 "我應(yīng)該與哪些組件共享狀態(tài)?"問題的組件。Provider的實(shí)現(xiàn)示例如下:
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>
}
復(fù)制代碼
Provider 在管理初始狀態(tài)和設(shè)置 Context 可訪問狀態(tài)的組件方面起著至關(guān)重要的作用。
接下來,你可以創(chuàng)建一個(gè)消費(fèi)者組件來使用狀態(tài):
消費(fèi)者組件
最后一個(gè)想和大家分享的是條件渲染模式。今天,人人都知道 React 中的條件渲染。它通過條件判斷來選擇組件進(jìn)行渲染。
但在實(shí)際使用中我們的用法常常是錯(cuò)誤的:
// 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;
復(fù)制代碼
你是否注意到,這里我們將基于條件的邏輯耦合到了 JSX 代碼片段中。通常,你不應(yīng)該在 JSX 代碼中中添加任何與計(jì)算相關(guān)的邏輯,而只將與 UI 渲染相關(guān)的內(nèi)容放在其中。
解決這個(gè)問題的方法之一是使用條件渲染組件模式。創(chuàng)建一個(gè)可重用的 React 組件,該組件可以根據(jù)條件渲染兩個(gè)不同的組件。它的實(shí)現(xiàn)過程如下:
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;
}
復(fù)制代碼
我們創(chuàng)建了一個(gè)可以按條件渲染兩個(gè)組件的組件。當(dāng)我們將其集成到其他組件中時(shí),會使代碼更簡潔,因?yàn)闊o需在 React 組件中加入復(fù)雜的渲染邏輯。你可以像下面這樣使用它:
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"
/>
);
}
復(fù)制代碼
實(shí)際的輸入如下:
掌握這五種設(shè)計(jì)模式,為 2024 年做好充分準(zhǔn)備,構(gòu)建出可擴(kuò)展和可維護(hù)的應(yīng)用吧。
如果你想詳細(xì)深入本文中討論的模式,請隨時(shí)查看我在Bit Cloud的空間:
https://bit.cloud/lakinduhewa/react-design-patterns
感謝你的閱讀!
原文鏈接:2024年,你應(yīng)該知道的5種React設(shè)計(jì)模式_架構(gòu)/框架_InfoQ精選文章
自:coderwhy
前面說過,整個(gè)前端已經(jīng)是組件化的天下,而CSS的設(shè)計(jì)就不是為組件化而生的,所以在目前組件化的框架中都在需要一種合適的CSS解決方案。
事實(shí)上,css一直是React的痛點(diǎn),也是被很多開發(fā)者吐槽、詬病的一個(gè)點(diǎn)。
在組件化中選擇合適的CSS解決方案應(yīng)該符合以下條件:
在這一點(diǎn)上,Vue做的要遠(yuǎn)遠(yuǎn)好于React:
Vue在CSS上雖然不能稱之為完美,但是已經(jīng)足夠簡潔、自然、方便了,至少統(tǒng)一的樣式風(fēng)格不會出現(xiàn)多個(gè)開發(fā)人員、多個(gè)項(xiàng)目采用不一樣的樣式風(fēng)格。
相比而言,React官方并沒有給出在React中統(tǒng)一的樣式風(fēng)格:
在這篇文章中,我會介紹挑選四種解決方案來介紹:
內(nèi)聯(lián)樣式是官方推薦的一種css樣式的寫法:
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標(biāo)題</h2>
<p style={{color: "green", textDecoration: "underline"}}>我是一段文字描述</p>
</div>
)
}
}
內(nèi)聯(lián)樣式的優(yōu)點(diǎn):
內(nèi)聯(lián)樣式的缺點(diǎn):
所以官方依然是希望內(nèi)聯(lián)合適和普通的css來結(jié)合編寫;
普通的css我們通常會編寫到一個(gè)單獨(dú)的文件。
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的標(biāo)題</h2>
<p className="desc">我是App中的一段文字描述</p>
<Home/>
</div>
)
}
}
App.css中編寫React樣式代碼:
.title {
color: red;
font-size: 20px;
}
.desc {
color: green;
text-decoration: underline;
}
這樣的編寫方式和普通的網(wǎng)頁開發(fā)中編寫方式是一致的:
比如編寫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標(biāo)題</h2>
<span className="desc">我是Home中的span段落</span>
</div>
)
}
}
又編寫了Home.css的樣式代碼:
.title {
color: orange;
}
.desc {
color: purple;
}
最終樣式之間會相互層疊,只有一個(gè)樣式會生效;
css modules并不是React特有的解決方案,而是所有使用了類似于webpack配置的環(huán)境下都可以使用的。
但是,如果在其他項(xiàng)目中使用,那么我們需要自己來進(jìn)行配置,比如配置webpack.config.js中的modules: true等。
但是React的腳手架已經(jīng)內(nèi)置了css modules的配置:
使用的方式如下:
css modules用法
這種css使用方式最終生成的class名稱會全局唯一:
生成的代碼結(jié)構(gòu)
css modules確實(shí)解決了局部作用域的問題,也是很多人喜歡在React中使用的一種方案。
但是這種方案也有自己的缺陷:
如果你覺得上面的缺陷還算OK,那么你在開發(fā)中完全可以選擇使用css modules來編寫,并且也是在React中很受歡迎的一種方式。
實(shí)際上,官方文檔也有提到過CSS in JS這種方案:
在傳統(tǒng)的前端開發(fā)中,我們通常會將結(jié)構(gòu)(HTML)、樣式(CSS)、邏輯(JavaScript)進(jìn)行分離。
當(dāng)然,這種開發(fā)的方式也受到了很多的批評:
批評聲音雖然有,但是在我們看來很多優(yōu)秀的CSS-in-JS的庫依然非常強(qiáng)大、方便:
目前比較流行的CSS-in-JS的庫有哪些呢?
目前可以說styled-components依然是社區(qū)最流行的CSS-in-JS庫,所以我們以styled-components的講解為主;
安裝styled-components:
yarn add styled-components
ES6中增加了模板字符串的語法,這個(gè)對于很多人來說都會使用。
但是模板字符串還有另外一種用法:標(biāo)簽?zāi)0遄址═agged Template Literals)。
我們一起來看一個(gè)普通的JavaScript的函數(shù):
function foo(...args) {
console.log(args);
}
foo("Hello World");
正常情況下,我們都是通過 函數(shù)名() 方式來進(jìn)行調(diào)用的,其實(shí)函數(shù)還有另外一種調(diào)用方式:
foo`Hello World`; // [["Hello World"]]
如果我們在調(diào)用的時(shí)候插入其他的變量:
foo`Hello ${name}`; // [["Hello ", ""], "kobe"];
在styled component中,就是通過這種方式來解析模塊字符串,最終生成我們想要的樣式的
styled-components的本質(zhì)是通過函數(shù)的調(diào)用,最終創(chuàng)建出一個(gè)組件:
比如我們正常開發(fā)出來的Home組件是這樣的格式:
<div>
<h2>我是Home標(biāo)題</h2>
<ul>
<li>我是列表1</li>
<li>我是列表2</li>
<li>我是列表3</li>
</ul>
</div>
我們希望給外層的div添加一個(gè)特殊的class,并且添加相關(guān)的樣式:
styled-components基本使用
另外,它支持類似于CSS預(yù)處理器一樣的樣式嵌套:
const HomeWrapper = styled.div`
color: purple;
h2 {
font-size: 50px;
}
ul > li {
color: orange;
&.active {
color: red;
}
&:hover {
background: #aaa;
}
&::after {
content: "abc"
}
}
`
最終效果如下
props可以穿透
定義一個(gè)styled組件:
const HYInput = styled.input`
border-color: red;
&:focus {
outline-color: orange;
}
`
使用styled的組件:
<HYInput type="password"/>
props可以被傳遞給styled組件
<HomeWrapper color="blue">
</HomeWrapper>
使用時(shí)可以獲取到傳入的color:
const HomeWrapper = styled.div`
color: ${props => props.color};
}
添加attrs屬性
const HYInput = styled.input.attrs({
placeholder: "請?zhí)顚懨艽a",
paddingLeft: props => props.left || "5px"
})`
border-color: red;
padding-left: ${props => props.paddingLeft};
&:focus {
outline-color: orange;
}
`
支持樣式的繼承
編寫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設(shè)置主題
在全局定制自己的主題,通過Provider進(jìn)行共享:
import { ThemeProvider } from 'styled-components';
<ThemeProvider theme={{color: "red", fontSize: "30px"}}>
<Home />
<Profile />
</ThemeProvider>
在styled組件中可以獲取到主題的內(nèi)容:
const ProfileWrapper = styled.div`
color: ${props => props.theme.color};
font-size: ${props => props.theme.fontSize};
`
vue中添加class
在vue中給一個(gè)元素添加動態(tài)的class是一件非常簡單的事情:
你可以通過傳入一個(gè)對象:
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
你也可以傳入一個(gè)數(shù)組:
<div v-bind:class="[activeClass, errorClass]"></div>
甚至是對象和數(shù)組混合使用:
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
react中添加class
React在JSX給了我們開發(fā)者足夠多的靈活性,你可以像編寫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": "")}>我是標(biāo)題</h2>
<h2 className={["title", (isActive ? "active": "")].join(" ")}>我是標(biāo)題</h2>
</div>
)
}
}
這個(gè)時(shí)候我們可以借助于一個(gè)第三方的庫: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'
我是@半糖學(xué)前端 ,專注前端技術(shù)領(lǐng)域分享,關(guān)注我和我一起學(xué)習(xí),共同進(jìn)步!
今,縱觀各類招聘網(wǎng)站上的前端職位,大家往往都會看到一個(gè)熟悉的字眼:React。雖然企業(yè)雇主也經(jīng)常會列出其他一些類似的前端框架選項(xiàng),但 React 的地位幾乎是雷打不動。
但面對這樣的現(xiàn)實(shí),請?jiān)徫沂冀K無法理解。除了流行,React 到底還有什么優(yōu)勢?
首先我想澄清一點(diǎn),我對 React 沒有任何敵意。我知道它挺好,而且如果需要開發(fā)龐大復(fù)雜的前端項(xiàng)目,我其實(shí)也并不抗拒使用 React。
React 的出現(xiàn),為其他框架當(dāng)前及未來的功能規(guī)劃奠定了基礎(chǔ)。Vue 3及其組合 API 明顯是受到 React hooks 的啟發(fā)。Svelte 中的很多約定就來自 React。至于我最喜愛的 Vue 框架 Nuxt,它的大部分設(shè)計(jì)思路都來自 React 的元框架 Next。總而言之,整個(gè)基于組件的前端設(shè)計(jì)和開發(fā)模型,特別是目前的整個(gè)生態(tài)系統(tǒng),都離不開 React 的鼎力支持。
但就個(gè)人觀點(diǎn),我還是覺得 React 像是那種開創(chuàng)了流派的祖師級老電影、音樂專輯或者電子游戲。它的偉大體現(xiàn)在那個(gè)時(shí)間點(diǎn)上的開創(chuàng)性,但時(shí)至今日自身的絕對價(jià)值其實(shí)已經(jīng)很有限了。
好吧,這話可能說得有點(diǎn)狠。畢竟一提到開創(chuàng)性的老電影,大家首先想到的就是《公民凱恩》,React 跟其他框架間的差距肯定不像《公民凱恩》相較于后來的經(jīng)典佳作那么大。我的觀點(diǎn)很簡單:
React 已經(jīng)老了,只是經(jīng)常用它的朋友們還沒有意識到它老到了什么程度、引發(fā)了哪些問題。
如果只用 React,那可能會覺得這框架不錯(cuò)啊,而且一直在努力改進(jìn)。確實(shí),React 在很多方面都是越來越好,但這并不能改變它的發(fā)展速度和功能上限已經(jīng)長期跟不上同類方案的事實(shí)。
總之一句話,React 表現(xiàn)得不錯(cuò),只是不像其他框架那么好。
假設(shè)大家身為某家科技初創(chuàng)公司的 CTO,或者是打算開發(fā)某網(wǎng)絡(luò)軟件新產(chǎn)品的個(gè)人創(chuàng)業(yè)者。
我們面前擺著新項(xiàng)目的藍(lán)圖,可以隨意選擇自己喜愛的技術(shù)進(jìn)行構(gòu)建。沒有約束,也不設(shè)產(chǎn)品生命周期限制,那么你會選擇哪一種前端框架?
(有些朋友可能會抬杠說,不用前端框架也行。但對于任何成規(guī)模、比較復(fù)雜的項(xiàng)目來說,不用前端框架肯定不是什么好主意。)
要做出選擇,先要考慮以下幾項(xiàng)條件:
而有沒有一種可能,從這么多角度來論證,其實(shí) React 并不是什么好選擇。
下面咱們逐一探討。
大家可以通過多種不同指標(biāo)來衡量性能。但無論關(guān)注哪個(gè)具體方面,React 都稱不上頂級水準(zhǔn)。Vue、Svelte、Solid、Inferno 等工具的性能總體上都要好于 React。根據(jù)實(shí)際需求,大家甚至可以通過 Alpine 或者 Petite Vue 等讓性能更上一層樓(雖然我覺得這兩種跟其他框架并不算同一類方案)。
React 的性能不佳應(yīng)該已經(jīng)是個(gè)共識,所以這里無需繼續(xù)贅述。所以我認(rèn)為,如果大家希望讓新項(xiàng)目擁有強(qiáng)大的性能表現(xiàn),那 React 就可以直接被排除在外。
假設(shè)你對前端框架一無所知,那 React 也絕對不是最好學(xué)、最容易上手的選項(xiàng)。
JSX的實(shí)質(zhì),就是笨拙地把 HTML 硬塞進(jìn) JavaScript 函數(shù)返回。你以為這就夠糟了?不,更糟的是你在 React 里不用 JSX。
除了 JSX,React 本身也有不少獨(dú)特的約束和毛病(比如提供兩種完全不同的語法,但二者完全無法互操作)。
在 React 中,其他前端框架能夠幫我們輕松打理的小事,往往還是需要手動干預(yù)或者借助大量樣板文件(甚至二者兼有)。
幾乎每種前端框架都把用戶設(shè)想成普通人,但 React 不同,它最早是專為 Facebook 的工程師們打造的。雖然經(jīng)過多年發(fā)展,它已經(jīng)成為一種比較通行的市場化產(chǎn)品,但即使到現(xiàn)在,這樣的出身仍然給 React 留下了深深的烙印。我們還是可以看到其中留下的早期決策與優(yōu)化痕跡。
至于其他問題,那可太多了。萬惡之源 useEffect,不能在 JSX 中使用某些標(biāo)準(zhǔn) HTML 屬性(因?yàn)?JSX 無法區(qū)分 React prop 和 HTML 屬性),記憶化,只能靠短路運(yùn)算符模仿出來的虛假條件,以及要求開發(fā)者自己防止無限循環(huán)等等……其實(shí)連這里的循環(huán)都是假的,必須靠數(shù)組方法才能實(shí)現(xiàn)。不說了,說不完。
這一點(diǎn)跟速度類似,但我覺得還是有必要區(qū)分開來。哪怕下載包大一點(diǎn),但實(shí)際使用時(shí)性能更好,那也沒啥問題。但 React 可不是這樣。
React 軟件包相當(dāng)臃腫,這個(gè)大家都知道了,我也不多廢話。
我想強(qiáng)調(diào)的是,有些人覺得 React 大多數(shù)情況下會從緩存中加載,所以綁定包大小無所謂。這種認(rèn)知最早是假的,后來現(xiàn)代瀏覽器讓它成了真,可最近的安全升級開始阻止域之間的緩存共享,所以又成了假的。
另外,Preact 雖然表現(xiàn)不錯(cuò),但還沒辦法跟 React 無縫對接。而且 Preact 的包大小跟其他前端框架比也沒有太大的優(yōu)勢。
雖然 React 對應(yīng)的企業(yè)應(yīng)用規(guī)模肯定是最大的,但我覺得吧,數(shù)量跟質(zhì)量并不是一回事。
從 Vue 到Svelte,再到Angular和 Ember,每一款主流前端框架都擁有類似的大規(guī)模執(zhí)行能力。他們的網(wǎng)站主頁上,也不乏一個(gè)個(gè)聲名顯赫的重量級客戶徽標(biāo)。
所以 React 沒什么特別的,只是案例最多罷了。
如果大家有很強(qiáng)的從眾心理,那我也無話可說。但客戶多真的不代表 React 就一定更優(yōu)秀,它只是出現(xiàn)在了充滿機(jī)會的時(shí)代。
沒錯(cuò),React 背后的社區(qū)規(guī)模最大,但這還是不足以支撐 React 就最好的結(jié)論。
大社區(qū)也有負(fù)面影響,特別是對 React 這類“無傾向性”框架而言。社區(qū)過大可能對應(yīng)著太多可供選擇的套餐,太多相互沖突、彼此對抗的觀點(diǎn),逼著用戶在其中站隊(duì),然后承受隨之而來的一切。
不僅如此,社區(qū)太大,甚至不一定能讓產(chǎn)品越變越好。
最初,更多的參與者確實(shí)能不斷帶來好的功能特性。但這里存在一個(gè)收益遞減點(diǎn)(即不斷上升的溝通成本,逐漸開始減慢、而非加快項(xiàng)目發(fā)展速度)。除此之外,社區(qū)的參與人數(shù)和社區(qū)質(zhì)量間也沒有必然關(guān)聯(lián)。
當(dāng)然,我理解想要去爆滿的餐廳吃飯那種心情,這似乎能給人一種安全感。畢竟哪怕不好吃,還有那么多人跟我一起上當(dāng)呢,這波不虧。但哪怕人再多,都無法改變“不好吃”這個(gè)基本事實(shí)。這跟愿意來吃的人是多是少,真的沒什么關(guān)系。所以我們用不著非往最火爆的餐廳去擠,挑一家適合自己的、安靜享受一頓美食,不就足夠了?
有些人總擔(dān)心自己使用的框架,會在某一天突然消失,由此失去支持和維護(hù)。對他們來說,出自 Facebook 系的 React 天然值得信任。但問題是,其他前端項(xiàng)目的資金支持有那么不堪嗎?
Angular 的背后可是谷歌,Vue 則是歷史上最成功、資金也最充裕的開源項(xiàng)目之一。Vercel 目前至少雇用了兩位 Svelte 維護(hù)者(其中包括 Svelte 的締造者)全職負(fù)責(zé)項(xiàng)目管理。至于 Solid,已經(jīng)擁有超過 100 名貢獻(xiàn)者和至少六家主要企業(yè)贊助商。
所以,資金支持對各大主流前端框架來說都不是問題,React 在這方面同樣不算占優(yōu)。
React 確實(shí)是應(yīng)用范圍最廣的前端框架,知名度也是一時(shí)無兩。但在今年的 JS 現(xiàn)狀調(diào)查報(bào)告中,React 的開發(fā)者滿意度已經(jīng)不及 Solid 和 Svelte。至于受關(guān)注度,React 落后于 Svelte、Solid 和 Vue,甚至已經(jīng)跌破 50%。
多年以來,React 的用戶滿意度和關(guān)注度一直在穩(wěn)步下降,采用率也停滯不前。
當(dāng)然,這類調(diào)查問卷只能作為參考,在問題的表述方式上稍做手腳就能得出不同的答案。但其他同類調(diào)查也發(fā)現(xiàn)了類似的趨勢。在 Stack Overflow 的調(diào)查中,React 的受歡迎度遠(yuǎn)低于 Svelte,僅略微高于 Vue。
有趣的是,Scott Tolinski 最近提到,他的一位開發(fā)者放棄了薪酬豐厚的 React 職位,寧愿拿一半的工資也要加入 Tolinski 領(lǐng)導(dǎo)的 SvelteKit 項(xiàng)目。
當(dāng)然了,并不能用這個(gè)例子證明開發(fā)者連錢都愿意放棄,就為了離 React 遠(yuǎn)一點(diǎn)。但至少能夠看出,React 帶給開發(fā)者的使用感受實(shí)在稱不上好。
這方面,React 確實(shí)堪稱一騎絕塵。如果想讓新人快速理解之前的開發(fā)資產(chǎn),那 React 的優(yōu)勢可太明顯了。
但是吧,我覺得單這一點(diǎn)不足以讓 React 脫穎而出。
鑒于選擇 React 之后,應(yīng)用程序的綁定包本身就更大、速度更慢而且復(fù)雜性更高,用這么多弊端來換取所謂項(xiàng)目接管期間的一點(diǎn)點(diǎn)便利,無疑是在犧牲長遠(yuǎn)收益尋求眼下省事。翻譯翻譯,這不就是典型的技術(shù)債務(wù)嗎?
現(xiàn)在省下的幾個(gè)禮拜,未來可能需要幾個(gè)月甚至幾年來償還。所以雖然 React 在這方面占優(yōu),但大家最好還是認(rèn)真核算一下,沒辦法無腦選它。
另外,了解 React 的朋友想上手其他前端框架,應(yīng)該不是什么難事。沒錯(cuò),不同框架間總有一些細(xì)微差別和小怪癖,但其中遵循的設(shè)計(jì)理念還是大體相同的。任何熟悉 React 的優(yōu)秀開發(fā)者,也都能在其他框架上獲得同樣的工作成效。
我承認(rèn),商業(yè)世界從來沒有好壞,只有權(quán)衡取舍。開發(fā)速度很重要,前面提到的每一點(diǎn)也都很重要。所以,您的實(shí)際情況可能證明,哪怕是 React 速度更慢、綁定包更大、復(fù)雜度更高,它也仍然值得選擇。是的,我都理解。
但大家做出的選擇,最終也將成為與競爭對手在市場上搏殺時(shí)的一張牌。選得好就是好牌,反之亦然。如果大多數(shù)對手也選擇 React,那大家就是爛牌對局、菜雞互啄。
而如果能選得更好,也許就能壓制對方的牌形。
因?yàn)楹芏嗳嗽谧鲞x擇時(shí),往往是比較草率的。React 之所以現(xiàn)在受歡迎,就是因?yàn)?React 之前受歡迎。
在真正占領(lǐng)市場之前,人們其實(shí)是出于其他理由去選擇 React 的。也許因?yàn)樗芙鉀Q開發(fā)者面對的實(shí)際問題,也許因?yàn)樗容^新奇有趣,或者是其他什么原因。但可以肯定的是,當(dāng)時(shí)人們選 React 絕不是看中它更好就業(yè),或者是市場普及率最高的框架。而時(shí)過境遷,現(xiàn)在大家再選擇它,唯一的理由就是它夠老、夠踏實(shí)。
企業(yè)選它,因?yàn)槿瞬攀袌錾隙?React 的群體很大;求職者學(xué)它,是因?yàn)槿瞬攀袌錾掀髽I(yè)想要招聘 React 開發(fā)者。這是個(gè)自我強(qiáng)化的循環(huán),一個(gè)自我實(shí)現(xiàn)的預(yù)言。
于是 React 成了默認(rèn)選項(xiàng),只要沒有充分的理由,更多人只會以無所謂的態(tài)度接著用。
while (reactIsPopular) {
reactIsPopular = true
}
復(fù)制代碼
“畢竟沒人會因?yàn)橛昧?React 而被解雇”,這話倒也沒錯(cuò)。React 是個(gè)安全的選擇,可能不是每個(gè)人的最愛,但至少不會惹出大麻煩。它,還是能干活的。
所以只要沒有強(qiáng)烈的業(yè)務(wù)需求,求職者和招聘方都可以接受圍繞 React 建立起來的行業(yè)現(xiàn)狀。只要能把雙方對接起來,React 的作用就已經(jīng)達(dá)到了。
我其實(shí)一直在關(guān)注事態(tài)的變化。
但要說答案,我也沒有。根據(jù)之前提到的幾份調(diào)查報(bào)告,React 的采用率確實(shí)出現(xiàn)了停滯。也不能說不再增長,只能說 React 的增長跟不斷擴(kuò)大的市場本體之間保持了同步,三年來份額一直維持在 80%左右。
但終有一天,我相信這種循環(huán)會中斷。但我不知道具體的導(dǎo)火索會是什么。
也許是個(gè)量變引發(fā)質(zhì)變的過程。回想起來,很多趨勢來得看似突然,但實(shí)際上一直在隨時(shí)間推移而積蓄力量。也許其他前端框架更好地證明了自己,并逐漸削平了 React 在人才儲備方面的優(yōu)勢,于是企業(yè)開始向其他方案敞開懷抱。
也可能會有部分企業(yè)觸及 React 的性能上限,并結(jié)合業(yè)務(wù)需求轉(zhuǎn)向性能更強(qiáng)的選項(xiàng)。比方說,如果公司的業(yè)務(wù)對移動端性能有著極高要求,而且必須能夠在設(shè)備配置差、網(wǎng)絡(luò)不穩(wěn)定的區(qū)域內(nèi)提供良好體驗(yàn),那 React 差出的這部分性能就足以促成改變。
但對大多數(shù)企業(yè)來說,情況還沒那么極端。大部分舊項(xiàng)目實(shí)在沒必要做遷移,性能困擾雖然偶然出現(xiàn),也絕不至于要因此推動大規(guī)模重構(gòu)。所以繼續(xù)用 React 完全沒有問題,它在很多場景下已經(jīng)完全夠用了。
所以沒準(zhǔn)市場就固化在了這一刻,再沒有誰能真正挑戰(zhàn) React 的統(tǒng)治地位。等到它真正宣布退位的時(shí)候,也許我們已經(jīng)徹底拋棄了前端框架,那時(shí)候主流瀏覽器、特別是 JS 已經(jīng)擴(kuò)展到了不需要它們的地步。這就是所謂后框架時(shí)代。
還有一種可能,React 在事實(shí)上已經(jīng)過時(shí)了,只是在宏觀統(tǒng)計(jì)上還體現(xiàn)不出來。目前人才市場上的招聘需求,反映的是企業(yè)在很久之前做出的框架選擇。正如核酸測試體現(xiàn)的是幾天、甚至幾周之前的區(qū)域內(nèi)疫情狀況一樣,目前的招聘態(tài)勢也許也存在滯后。
我不知道未來的前端會是什么樣子,應(yīng)該沒人能做出準(zhǔn)確的預(yù)言。但可以肯定的是,React 還能風(fēng)光上好一陣子。
如果大家正在學(xué)習(xí)前端開發(fā),想用這個(gè)為自己找份工作或提升職業(yè)水平,那 React 是個(gè)不錯(cuò)的選項(xiàng)、也是非常安全的方向。
但我也希望能有越來越多的開發(fā)者積極探索其他選項(xiàng),也希望企業(yè)能給他們更多嘗試的機(jī)會。近年來,前端開發(fā)領(lǐng)域的驚喜都來自 Vue 和 Svelte。以我個(gè)人的感受和經(jīng)驗(yàn),React 只是能干活,并沒讓工作變得更有趣。
而且越來越多的開發(fā)者也意識到了這個(gè)問題,開始嘗試接觸其他框架、體驗(yàn)它們的不同特性,也反過來意識到 React 是有多么老邁和遲鈍。即使單純從推動未來前端開發(fā)者多樣性的角度出發(fā),我也真心建議大家用用別的框架方案。
原文鏈接:
https://joshcollinsworth.com/blog/self-fulfilling-prophecy-of-react
*請認(rèn)真填寫需求信息,我們會在24小時(shí)內(nèi)與您取得聯(lián)系。