视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
4个开发React应用的实用技巧
2020-11-27 14:05:41 责编:小采
文档


背景

Hooks 自推出以来就很火, 它改变了我们编写React 代码的方式, 有助于我们写更简洁的代码。

今天这边文章不是说Hooks的,Hooks之外, 还有很多实用的技巧可以帮助我们编写简洁清晰的代码。

今天我就整理了几个使用的技巧,其中有些也是我在公司项目中实践的,现在整理出来分享给大家, 希望对大家有所启发

正文

1. 使用字符串来定义一个React元素

举个简单的例子:

// 我们可以通过把一个字符串'p' 赋值给一个变量, 就像:
import React from 'react'

const MyComponent = 'p'

function App() {
 return (
 <>
 <MyComponent>
 <h3>I am inside a {'<p />'} element</h3>
 </MyComponent>
 </>
 )
}

React 内部会调用 React.createElement, 使用这个字符串来生成这个元素。

另外, 你也可以显式的定义component 来决定渲染的内容, 比如:

// 定义一个MyComponent
function MyComponent({ component: Component = 'p', 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="p" name="KK" age={18} email="xxx@gmail.com">
 </>
 )
}

这种方式, 你也可以传入一个自定义的组件, 比如:

function Dashboard({ children }) {
 return (
 <p style={{ padding: '25px 12px' }}>
 {children}
 </p>
 )
}

function App() {
 return (
 <>
 <MyComponent component={Dashboard} name="KK" age={18} email="xxx@gmail.com">
 </>
 )
}

如果你遇到处理一类相似的元素或者组件,可以通过这种自定义的方式抽象出来,简化你的代码。

举个现实的例子:

比如我们现在要做一个货物打包的需求, 可以单个打, 也可以批量打, 针对共同点可以写自定义组件:

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//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
 }
}

使用方式:

<ErrorBoundary>
 <MyWidget />
</ErrorBoundary>

Live Demo By Dan Abramov:

https://codepen.io/gaearon/pen/wqvxGa?editors=0010

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 (
 <p style={style} {...rest}>
 <h2>
 This is my component and I am expecting some styles.
 </h2>
 </p>
 )
}

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')}}
 </>
 )
})

十分的方便。

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 (
 <p style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
 <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
 </p>
 );
 }
}

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 (
 <p style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
 <Cat mouse={this.state} />
 </p>
 );
 }
}

巴适~ 简单粗暴, 一分钟完成任务。

可是,如果下次产品再要想加条狗呢

以上的例子,虽然可以完成了猫追鼠标的需求,还没有达到以可复用的方式真正封装行为的目标。

当我们想要鼠标位置用于不同的用例时,我们必须创建一个新的组件,专门为该用例呈现一些东西.

这也是 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 (
 <p style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
 {this.props.render(this.state)}
 </p>
 );
 }
}

class MouseTracker extends React.Component {
 render() {
 return (
 <p>
 <h1>移动鼠标!</h1>
 <Mouse render={mouse => (
 <Cat mouse={mouse} />
 )}/>
 </p>
 );
 }
}

提供了一个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 (
 <p>
 <h1>Move the mouse around!</h1>
 <Mouse render={this.renderTheCat} />
 </p>
 );
 }
}

5.组件性能

性能优化是永恒的主题, 这里不一一细说, 提供积分资源供你参考:

  • React.memo https://reactjs.org/docs/reac...
  • React.useMemo https://flaviocopes.com/react...
  • React.useCallback https://reactjs.org/docs/hook...
  • React.PureComponent https://reactjs.org/docs/reac...
  • Optimizing performance https://reactjs.org/docs/opti...
  • 总结

    以上几点都是我们经常要使用的技巧, 简单实用, 分享给大家, 希望能给大家带来一些帮助或启发,谢谢。

    推荐阅读:React在线手册

    下载本文
    显示全文
    专题