4個開發 React 應用的實用技巧
背景
Hooks 自推出以來就很火, 它改變了我們編寫react程式碼的方式, 有助於我們寫更簡潔的程式碼。
今天這邊文章不是說Hooks的,Hooks之外, 還有很多實用的技巧可以幫助我們編寫簡潔清晰的程式碼。
今天我就整理了幾個使用的技巧,其中有些也是我在公司專案中實踐的,現在整理出來分享給大家, 希望對大家有所啟發。
1. 使用字串來定義一個react元素
舉個簡單的例子:
// 我們可以通過把一個字串'div' 賦值給一個變數, 就像:
import React from 'react'
const MyComponent = 'div'
function App() {
return (
<>
<MyComponent>
<h3>I am inside a {'<div />'} element</h3>
</MyComponent>
</>
)
}
React 內部會呼叫React.createElement, 使用這個字串來生成這個元素。
另外, 你也可以顯式的定義component來決定渲染的內容, 比如:
// 定義一個MyComponent
function MyComponent({ component: Component = 'div', name, age, email }) {
return (
<Component>
<h1>Hi {name} </h1>
<>
<h6>You are {age} years old</h6>
<small>Your email is {email}</small>
</>
</Component>
)
}
適用方式:
function App() {
return (
<>
<MyComponent component="div" name="KK" age={18} email="[email protected]">
</>
)
}
這種方式, 你也可以傳入一個自定義的元件, 比如:
function Dashboard({ children }) {
return (
<div style={{ padding: '25px 12px' }}>
{children}
</div>
)
}
function App() {
return (
<>
<MyComponent component={Dashboard} name="KK" age={18} email="[email protected]">
</>
)
}
如果你遇到處理一類相似的元素或者元件,可以通過這種自定義的方式抽象出來,簡化你的程式碼。
舉個現實的例子:
比如我們現在要做一個貨物打包的需求, 可以單個打, 也可以批量打, 針對共同點可以寫自定義元件:
import React from 'react'
import withTranslate from '@components/withTranslate'
import PackComponent from './PackComponent'
import usePack, { check } from './usePack'
let PackEditor = (props) => {
const packRes = usePack(props)
return (
<PackComponent
{...packRes}
/>
)
}
PackEditor = withTranslate(PackEditor)
PackEditor.check = check
export default PackEditor
這樣在不同的業務模組中, 就可以靈活的使用了, 非常方便。
2. 定義錯誤邊界
在JavaScript裡,我們都是使用try/catch來捕捉可能發生的異常,在catch中處理錯誤。 比如:
function getFromLocalStorage(key, value) {
try {
const data = window.localStorage.get(key)
return jsON.parse(data)
} catch (error) {
console.error
}
}
這樣, 即便發生了錯誤, 我們的應用也不至於崩潰白屏。
React 歸根結底也是JavaScript,本質上沒什麼不同, 所以同樣的使用try/catch也沒有問題。
然而, 由於React 實現機制的原因, 發生在元件內部的Javascript 錯誤會破壞內部狀態, render會產生錯誤:https://github.com/facebook/react/issues/4026
基於以上原因,React 團隊引入了Error Boundaries:https://reactjs.org/docs/
Error boundaries, 其實就是React元件, 你可以用找個元件來處理它捕捉到的任何錯誤資訊。
當元件樹崩潰的時候,也可以顯示你自定義的UI,作為回退。
看 React 官方提供的例子:https://reactjs.org/docs/
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
logErrorToMyService(error, errorInfo)
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>
}
return this.props.children
}
}
使用方式:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
Live Demo By Dan Abramov:
https://codepen.io/gaearon/
3.高階元件
通俗點講, 所謂高階元件就是, 你丟一個元件進去, 增加一些屬性或操作, 再丟出來。
一般來說, 你可以把一些具備共同點的元件抽象成一個高階元件, 然後再不同的模組中複用。
比如, 我們的系統中, 有一類按鈕要加個border, 很多地方都要用到, 我們把它抽象出來:
import React from 'react'
// Higher order component
const withBorder = (Component, customStyle) => {
class WithBorder extends React.Component {
render() {
const style = {
border: this.props.customStyle ? this.props.customStyle.border : '3px solid teal'
}
return <Component style={style} {...this.props} />
}
}
return WithBorder
}
function MyComponent({ style, ...rest }) {
return (
<div style={style} {...rest}>
<h2>
This is my component and I am expecting some styles.
</h2>
</div>
)
}
export default withBorder(MyComponent, { border: '4px solid teal' })
經過withBorder裝飾的MyComponent元件, 就具備了統一border這項功能, 後面如果如果要做修改, 就可以在這個中間層統一處理, 非常方便。
在我的專案裡, 也用了一些高階元件, 舉個具體的例子:
PackEditor = withTranslate(PackEditor)
我們的這個PackEditor就是一個增強過的元件, 增加了什麼功能呢?
正如名字表述的,withTranslate, 增加了一個翻譯功能, 下面也給大家看看這個元件是怎麼實現的:
import React from 'react'
import { Provider } from 'react-redux'
import { injectIntl } from 'react-intl'
import { store } from '@redux/store'
import { Intl } from './Locale'
const withTranslate = BaseComponent => (props) => {
// avoid create a new component on re-render
const IntlComponent = React.useMemo(() => injectIntl(
({ intl, ...others }) => (
<BaseComponent
intl={intl}
translate={(id, values = {}) => { // 注入翻譯方法
if (!id) { return '' }
return intl.formatMessage(
typeof id === 'string' ? { id } : id,
values
)
}}
{...others}
/>
)
), [])
IntlComponent.displayName = `withTranslate(${BaseComponent.displayName || 'BaseComponent'})`
return (
<Provider store={store}>
<Intl>
<IntlComponent
{...props}
/>
</Intl>
</Provider>
)
}
export default withTranslate
用法很靈過:
const Editor = withTranslate(({
// ...
translate,
}) => {
// ...
return (
<>
{translate('xxx')}}
</>
)
})
十分的方便。
資源搜尋網站大全 http://www.szhdn.com 廣州VI設計公司https://www.houdianzi.com
4. Render props
Rrender prop 是指一種在 React元件之間使用一個值為函式的 prop共享程式碼的簡單技術, 和 HOC 類似, 都是元件間的邏輯複用問題。
更具體地說,Render prop 是一個用於告知元件需要渲染什麼內容的函式。
下面看一下簡單的例子:
以下元件跟蹤 Web 應用程式中的滑鼠位置:
class Mouse extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
<p>The current mouse position is ({this.state.x}, {this.state.y})</p>
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<>
<h1>移動滑鼠!</h1>
<Mouse />
</>
);
}
}
當游標在螢幕上移動時,元件顯示其(x,y)座標。
現在的問題是:
我們如何在另一個元件中複用這個行為?
換個說法,若另一個元件需要知道滑鼠位置,我們能否封裝這一行為,以便輕鬆地與其他元件共享它 ??
假設產品想要這樣一個功能: 在螢幕上呈現一張在螢幕上追逐滑鼠的貓的圖片。
我們或許會使用 <Cat mouse={{ x, y }} prop 來告訴元件滑鼠的座標以讓它知道圖片應該在螢幕哪個位置。
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
這個需求如此簡單,你可能就直接修改Mouse元件了:
class Mouse extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
<Cat mouse={this.state} />
</div>
);
}
}
巴適~ 簡單粗暴, 一分鐘完成任務。
可是,如果下次產品再要想加條狗呢?
以上的例子,雖然可以完成了貓追滑鼠的需求,還沒有達到以可複用的方式真正封裝行為的目標。
當我們想要滑鼠位置用於不同的用例時,我們必須建立一個新的元件,專門為該用例呈現一些東西.
這也是 render prop 的來歷:
我們可以提供一個帶有函式 prop 的<Mouse>元件,它能夠動態決定什麼需要渲染的,而不是將 <Cat>硬編碼到 <Mouse> 元件裡.
修改一下上面的程式碼:
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移動滑鼠!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
提供了一個render 方法,讓動態決定什麼需要渲染。
事實上,render prop 是因為模式才被稱為 render prop ,不一定要用名為 render 的 prop 來使用這種模式。
任何被用於告知元件需要渲染什麼內容的函式 prop, 在技術上都可以被稱為 "render prop".
另外,關於 render prop 一個有趣的事情是你可以使用帶有 render prop 的常規元件來實現大多數高階元件 (HOC)。
例如,如果你更喜歡使用 withMouse HOC 而不是 <Mouse> 元件,你可以使用帶有 render prop 的常規 <Mouse> 輕鬆建立一個:
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
);
}
}
}
也是非常的簡潔清晰。
有一點需要注意的是, 如果你在定義的render函式裡建立函式, 使用 render prop 會抵消使用 React.PureComponent 帶來的優勢。
因為淺比較 props 的時候總會得到 false,並且在這種情況下每一個 render 對於 render prop 將會生成一個新的值。
class Mouse extends React.PureComponent {
// 與上面相同的程式碼......
}
class MouseTracker extends React.Component {
render() {
return (
<>
<Mouse render={mouse => ( // 這是不好的! 每個渲染的 `render` prop的值將會是不同的。
<Cat mouse={mouse} />
)}/>
</>
);
}
}
在這樣例子中,每次 <MouseTracker> 渲染,它會生成一個新的函式作為 <Mouse render> 的 prop,因而在同時也抵消了繼承自 React.PureComponent 的 <Mouse> 元件的效果.
為了繞過這一問題,有時你可以定義一個 prop 作為例項方法,類似這樣:
class MouseTracker extends React.Component {
renderTheCat(mouse) {
return <Cat mouse={mouse} />;
}
render() {
return (
<div>
<h1>Move the mouse around!</h1>
<Mouse render={this.renderTheCat} />
</div>
);
}
}