这篇文章将为大家详细讲解有关开发React应用的技巧,文章内容质量较高,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
背景
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。由于 React的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
正文
1. 使用字符串来定义一个React元素
举个简单的例子:
// 我们可以通过把一个字符串'p' 赋值给一个变量, 就像:
import React from 'react'
const MyComponent = 'p'
function App() {
return (
<>
<MyComponent>
<h4>I am inside a {'<p />'} element</h4>
</MyComponent>
</>
)
}
React 内部会调用 React.createElement
, 使用这个字符串来生成这个元素。
另外, 你也可以显式的定义component
来决定渲染的内容, 比如:
// 定义一个MyComponent
function MyComponent({ component: Component = 'p', name, age, email }) {
return (
<Component>
<h2>Hi {name} </h2>
<>
<h7>You are {age} years old</h7>
<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/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 <h2>Something went wrong.</h2>
}
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}>
<h3>
This is my component and I am expecting some styles.
</h3>
</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 (
<>
<h2>移动鼠标!</h2>
<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>
<h2>移动鼠标!</h2>
<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>
<h2>Move the mouse around!</h2>
<Mouse render={this.renderTheCat} />
</p>
);
}
}
5.组件性能
性能优化是永恒的主题, 这里不一一细说, 提供积分资源供你参考:
以上就是开发React应用的技巧,看完之后是否有所收获呢?如果想了解更多相关内容,欢迎关注亿速云行业资讯,感谢各位的阅读。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。