干貨!幾個灰常實用的React實踐技巧(收藏)

Hooks 自推出以來就很火, 它改變了我們編寫React 代碼的方式, 有助于我們寫更簡潔的代碼。
今天這邊文章不是說Hooks的,Hooks之外, 還有很多實用的技巧可以幫助我們便攜簡潔實用的代碼。
今天我就整理了8個使用的技巧,其中有些也是我在公司項目中實踐的,現(xiàn)在整理出來分享給大家, 希望對大家有所啟發(fā)。
1. 使用字符串來定義一個React元素
舉個簡單的例子:
// 我們可以通過把一個字符串'div' 賦值給一個變量, 就像:
import React from 'react'
const MyComponent = 'div'
function App() {
return (
<>
<MyComponent>
<h3>I am inside a {'<div />'} elementh3>
MyComponent>
>
)
}
React 內(nèi)部會調用 React.createElement, 使用這個字符串來生成這個元素。
另外, 你也可以顯式的定義component 來決定渲染的內(nèi)容, 比如:
// 定義一個MyComponent
function MyComponent({ component: Component = 'div', name, age, email }) {
return (
<Component>
<h1>Hi {name} h1>
<>
<h6>You are {age} years oldh6>
<small>Your email is {email}small>
>
Component>
)
}
適用方式:
function App() {
return (
<>
>
)
}
這種方式, 你也可以傳入一個自定義的組件, 比如:
function Dashboard({ children }) {
return (
<div style={{ padding: '25px 12px' }}>
{children}
div>
)
}
function App() {
return (
<>
>
)
}
如果你遇到處理一類相似的元素或者組件,可以通過這種自定義的方式抽象出來,簡化你的代碼。
舉個現(xiàn)實的例子:
比如我們現(xiàn)在要做一個貨物打包的需求, 可以單個打, 也可以批量打, 針對共同點可以寫自定義組件:
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
這樣在不同的業(yè)務模塊中, 就可以靈活的使用了, 非常方便。
2. 定義錯誤邊界
在Javascript里,我們都是使用 try/catch 來捕捉可能發(fā)生的異常,在catch中處理錯誤。比如:
function getFromLocalStorage(key, value) {
try {
const data = window.localStorage.get(key)
return JSON.parse(data)
} catch (error) {
console.error
}
}
這樣, 即便發(fā)生了錯誤, 我們的應用也不至于崩潰白屏。
React 歸根結底也是Javascript,本質上沒什么不同, 所以同樣的使用try/catch ?也沒有問題。
然而, 由于React 實現(xiàn)機制的原因, 發(fā)生在組件內(nèi)部的Javascript 錯誤會破壞內(nèi)部狀態(tài), render會產(chǎn)生錯誤:
https://github.com/facebook/react/issues/4026
基于以上原因,React 團隊引入了Error Boundaries:
https://reactjs.org/docs/error-boundaries.html
Error boundaries, 其實就是React組件, 你可以用找個組件來處理它捕捉到的任何錯誤信息。
當組件樹崩潰的時候,也可以顯示你自定義的UI,作為回退。
看 React 官方提供的例子:https://reactjs.org/docs/error-boundaries.html#introducing-error-boundaries
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
}
}
使用方式:
<MyWidget />
ErrorBoundary>
Live Demo By Dan Abramov:
https://codepen.io/gaearon/pen/wqvxGa?editors=0010
3.高階組件
通俗點講, 所謂高階組件就是, 你丟一個組件進去, 增加一些屬性或操作, 再丟出來。
一般來說, 你可以把一些具備共同點的組件抽象成一個高階組件, 然后再不同的模塊中復用。
比如, 我們的系統(tǒng)中, 有一類按鈕要加個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' })
經(jīng)過withBorder裝飾的MyComponent組件, 就具備了統(tǒng)一border這項功能, 后面如果如果要做修改, 就可以在這個中間層統(tǒng)一處理, 非常方便。
在我的項目里, 也用了一些高階組件, 舉個具體的例子:
PackEditor = withTranslate(PackEditor)
我們的這個 PackEditor 就是一個增強過的組件, 增加了什么功能呢?
正如名字表述的, withTranslate, 增加了一個翻譯功能, 下面也給大家看看這個組件是怎么實現(xiàn)的:
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 }) => (
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 (
{...props}
/>
)
}
export default withTranslate
用法很靈過:
const Editor = withTranslate(({
// ...
translate,
}) => {
// ...
return (
<>
{translate('xxx')}}
>
)
})
十分的方便。
4. Render props
Rrender prop 是指一種在 React 組件之間使用一個值為函數(shù)的 prop 共享代碼的簡單技術, 和 HOC 類似, 都是組件間的邏輯復用問題。
更具體地說,Render prop 是一個用于告知組件需要渲染什么內(nèi)容的函數(shù)。
下面看一下簡單的例子:
以下組件跟蹤 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)坐標。
現(xiàn)在的問題是:
我們?nèi)绾卧诹硪粋€組件中復用這個行為?
換個說法,若另一個組件需要知道鼠標位置,我們能否封裝這一行為,以便輕松地與其他組件共享它??
假設產(chǎn)品想要這樣一個功能:在屏幕上呈現(xiàn)一張在屏幕上追逐鼠標的貓的圖片。
我們或許會使用 這個需求如此簡單,你可能就直接修改Mouse組件了: 巴適~ 簡單粗暴, 一分鐘完成任務。 可是,如果下次產(chǎn)品再要想加條狗呢? 以上的例子,雖然可以完成了貓追鼠標的需求,還沒有達到以可復用的方式真正封裝行為的目標。 當我們想要鼠標位置用于不同的用例時,我們必須創(chuàng)建一個新的組件,專門為該用例呈現(xiàn)一些東西. 這也是 render prop 的來歷: 我們可以提供一個帶有函數(shù) prop 的 修改一下上面的代碼: 提供了一個render 方法,讓動態(tài)決定什么需要渲染。 事實上,render prop 是因為模式才被稱為 render prop ,不一定要用名為 render 的 prop 來使用這種模式。 任何被用于告知組件需要渲染什么內(nèi)容的函數(shù) prop, 在技術上都可以被稱為 "render prop". 另外,關于 render prop 一個有趣的事情是你可以使用帶有 render prop 的常規(guī)組件來實現(xiàn)大多數(shù)高階組件 (HOC)。 例如,如果你更喜歡使用 withMouse HOC 而不是 也是非常的簡潔清晰。 有一點需要注意的是, 如果你在定義的render函數(shù)里創(chuàng)建函數(shù), 使用 render prop 會抵消使用 React.PureComponent 帶來的優(yōu)勢。 因為淺比較 props 的時候總會得到 false,并且在這種情況下每一個 render 對于 render prop 將會生成一個新的值。 在這樣例子中,每次 為了繞過這一問題,有時你可以定義一個 prop 作為實例方法,類似這樣: 性能優(yōu)化是永恒的主題, 這里不一一細說, 提供幾份資源供你參考:class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="" 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 (
);
}
}class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
);
}
}
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 (
移動鼠標!
)}/>
);
}
}function withMouse(Component) {
return class extends React.Component {
render() {
return (
)}/>
);
}
}
}class Mouse extends React.PureComponent {
// 與上面相同的代碼......
}
class MouseTracker extends React.Component {
render() {
return (
<>
)}/>
>
);
}
}class MouseTracker extends React.Component {
renderTheCat(mouse) {
return
}
render() {
return (
Move the mouse around!
);
}
}5.組件性能
總結
以上幾點都是我們經(jīng)常要使用的技巧, 簡單實用, 分享給大家, 希望能給大家?guī)硪恍椭騿l(fā),謝謝。
最后
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:
點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)
歡迎加我微信「qianyu443033099」拉你進技術群,長期交流學習...
關注公眾號「前端下午茶」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。

