首页

文章

如何开发由create-react-app 引导的应用

发布网友 发布时间:2022-04-19 09:46

我来回答

2个回答

懂视网 时间:2022-04-18 05:34

背景

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/facebook/react/issues/4026

1.png

基于以上原因,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在线手册

    热心网友 时间:2022-04-18 02:42

    说说React一个组件,有自己的结构,有自己的逻辑,有自己的样式,会依赖一些资源,会依赖某些其他组件。比如日常写一个组件,比较常规的方式:-通过前端模板引擎定义结构-JS文件中写自己的逻辑-CSS中写组件的样式-通过RequireJS、SeaJS这样的库来解决模块之间的相互依赖,那么在React中是什么样子呢?结构和逻辑在React的世界里,结构和逻辑交由JSX文件组织,React将模板内嵌到逻辑内部,实现了一个JS代码和HTML混合的JSX。结构在JSX文件中,可以直接通过React.createClass来定义组件:varCustomComponent=React.creatClass({render:function(){return();}});通过这种方式可以很方便的定义一个组件,组件的结构定义在render函数中,但这并不是简单的模板引擎,我们可以通过js方便、直观的操控组件结构,比如我想给组件增加几个节点:varCustomComponent=React.creatClass({render:function(){var$nodes=['h','e','l','l','o'].map(function(str){return({str});});return({$nodes});}});通过这种方式,React使得组件拥有灵活的结构。那么React又是如何处理逻辑的呢?逻辑写过前端组件的人都知道,组件通常首先需要相应自身DOM事件,做一些处理。必要时候还需要暴露一些外部接口,那么React组件要怎么做到这两点呢?事件响应比如我有个按钮组件,点击之后需要做一些处理逻辑,那么React组件大致上长这样:varButtonComponent=React.createClass({render:function(){return(屠龙宝刀,点击就送);}});点击按钮应当触发相应地逻辑,一种比较直观的方式就是给button绑定一个onclick事件,里面就是需要执行的逻辑了:functiongetDragonKillingSword(){//送宝刀}varButtonComponent=React.createClass({render:function(){return(屠龙宝刀,点击就送);}});但事实上getDragonKillingSword()的逻辑属于组件内部行为,显然应当包装在组件内部,于是在React中就可以这么写:varButtonComponent=React.createClass({getDragonKillingSword:function(){//送宝刀},render:function(){return(屠龙宝刀,点击就送);}});这样就实现内部事件的响应了,那如果需要暴露接口怎么呢?暴露接口事实上现在getDragonKillingSword已经是一个接口了,如果有一个父组件,想要调用这个接口怎么呢?父组件大概长这样:varImDaddyComponent=React.createClass({render:function(){return(//其他组件//其他组件);}});那么如果想手动调用组件的方法,首先在ButtonComponent上设置一个ref=""属性来标记一下,比如这里把子组件设置成,那么在父组件的逻辑里,就可以在父组件自己的方法中通过这种方式来调用接口方法:this.refs.getSwordButton.getDragonKillingSword();看起来屌屌哒~那么问题又来了,父组件希望自己能够按钮点击时调用的方法,那该怎么呢?配置参数父组件可以直接将需要执行的函数传递给子组件:然后在子组件中调用父组件方法:varButtonComponent=React.createClass({render:function(){return(屠龙宝刀,点击就送);}});子组件通过this.props能够获取在父组件创建子组件时传入的任何参数,因此this.props也常被当做配置参数来使用屠龙宝刀每个人只能领取一把,按钮点击一下就应该灰掉,应当在子组件中增加一个是否点击过的状态,这又应当处理呢?组件状态在React中,每个组件都有自己的状态,可以在自身的方法中通过this.state取到,而初始状态则通过getInitialState()方法来定义,比如这个屠龙宝刀按钮组件,它的初始状态应该是没有点击过,所以getInitialState方法里面应当定义初始状态clicked:false。而在点击执行的方法中,应当修改这个状态值为click:true:varButtonComponent=React.createClass({getInitialState:function(){//确定初始状态return{clicked:false};},getDragonKillingSword:function(){//送宝刀//修改点击状态this.setState({clicked:true});},render:function(){return(屠龙宝刀,点击就送);}});这样点击状态的维护就完成了,那么render函数中也应当根据状态来维护节点的样式,比如这里将按钮设置为disabled,那么render函数就要添加相应的判断逻辑:render:function(){varclicked=this.state.clicked;if(clicked)return(屠龙宝刀,点击就送);elsereturn(屠龙宝刀,点击就送);}小节这里简单介绍了通过JSX来管理组件的结构和逻辑,事实上React给组件还定义了很多方法,以及组件自身的生命周期,这些都使得组件的逻辑处理更加强大资源加载CSS文件定义了组件的样式,现在的模块加载器通常都能够加载CSS文件,如果不能一般也提供了相应的插件。事实上CSS、图片可以看做是一种资源,因为加载过来后一般不需要做什么处理。React对这一方面并没有做特别的处理,虽然它提供了InlineStyle的方式把CSS写在JSX里面,但估计没有多少人会去尝试,毕竟现在CSS样式已经不再只是简单的CSS文件了,通常都会去用Less、Sass等预处理,然后再用像postcss、myth、autoprefixer、cssmin等等后处理。资源加载一般也就简单粗暴地使用模块加载器完成了组件依赖组件依赖的处理一般分为两个部分:组件加载和组件使用组件加载React没有提供相关的组件加载方法,依旧需要通过标签引入,或者使用模块加载器加载组件的JSX和资源文件。组件使用如果细心,就会发现其实之前已经有使用的例子了,要想在一个组件中使用另外一个组件,比如在ParentComponent中使用ChildComponent,就只需要在ParentComponent的render()方法中写上就行了,必要的时候还可以传些参数。疑问到这里就会发现一个问题,React除了只处理了结构和逻辑,资源也不管,依赖也不管。是的,React将近两万行代码,连个模块加载器都没有提供,更与Angularjs,jQuery等不同的是,他还不带啥脚手架…没有Ajax库,没有Promise库,要啥啥没有…虚拟DOM那它为啥这么大?因为它实现了一个虚拟DOM(VirtualDOM)。虚拟DOM是干什么的?这就要从浏览器本身讲起如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScriptDOM结构,React又通过在这个虚拟DOM上实现了一个diff算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升
    玉米仁子饭产自哪里 中国期货交易所的交易品种有哪些? 历史要怎么读,有啥诀窍 高中历史诀窍 年终会活动策划方案 深度解析:第一财经回放,探索财经新风向 逆水寒手游庄园怎么邀请好友同住 逆水寒手游 逆水寒不同区可以一起组队吗? 逆水寒手游 逆水寒怎么进入好友世界? 逆水寒手游 逆水寒怎么去别人的庄园? 使用puppeteer实现将htmll转成pdf 内卷时代下的前端技术-使用JavaScript在浏览器中生成PDF文档 【译】将HTML转为PDF的几种实现方案 变形金刚08动画怎么样 变形金刚08动画的问题 变形金刚08动画日语版剧情介绍 高分!换显卡nvidia控制面板被我卸了,重新安装显卡驱动后没了nvidia控... 我的nvidia控制面板被卸载了 怎么找回啊 卸载后 这个画面看着很奇怪_百 ... 李卓彬工作简历 林少明工作简历 广东工业职业技术学院怎么样 郑德涛任职简历 唐新桂个人简历 土地入股的定义 ups快递客服电话24小时 贷款记录在征信保留几年? 安徽徽商城有限公司公司简介 安徽省徽商集团新能源股份有限公司基本情况 安徽省徽商集团有限公司经营理念 2019哈尔滨煤气费怎么有税? 快手删除的作品如何恢复 体育理念体育理念 有关体育的格言和理念 什么是体育理念 万里挑一算彩礼还是见面礼 绿萝扦插多少天后发芽 绿萝扦插多久发芽 扦插绿萝多久发芽 炖牛排骨的做法和配料 网络诈骗定罪标准揭秘 “流水不争先”是什么意思? mc中钻石装备怎么做 为什么我的MC里的钻石块是这样的?我想要那种。是不是版本的问题?如果是... 带“偷儿”的诗句 “君不见巴丘古城如培塿”的出处是哪里 带“奈何”的诗句大全(229句) 里翁行()拼音版、注音及读音 带“不虑”的诗句 “鲁肃当年万人守”的出处是哪里 无尘防尘棚 react-native image一定要设置 高宽 才能显示吗 react.js中怎么实现背景图适配手机 react import怎么使用 关于react项目中使用require导入图片不生效的问题 俄罗斯的先进战术导弹 巡航导弹巡航高度和飞行速度 闲鱼怎么找兴趣鱼塘 导弹可分为几种,用途,结构,材料? 什么叫巡航导弹 日本有类似飞鱼或者鱼叉的反舰导弹吗? 歼十有超视距导弹吗? 弹道导弹发射时要干什么,要输入哪些参数,能不能一竖起即可发射,如何缩短弹道导弹准备时间 法国飞鱼反舰导弹的导弹布局 韩国的玄武3-C技术真的超过中国了吗 射程最远的空空导弹是什么? 空空导弹问题 法国AS30L空地导弹采用了怎样的布局? 捷联惯导系统的工作原理及特点 AIM-120导弹详细资料 战略核导弹和战术核导弹有什么区别? node+react后台用webpack怎么做实时更新 React 之 config-overrides文件配置 404 Not Found 404 Not Found react native android 混合开发 怎么互相调用 react native键盘遮挡视图,怎么处理 如何在react中使用fetch调用接口 React createClass 和 Component 有什么区别 react native布局方式怎么写 reactnative怎么使用百分比布局 如何在React组件中引入百度地图API web前端开发需要学习什么知识? 闲鱼的鱼塘怎么不见了 空中舞星,双龙拱门是什么 游乐设备现在有哪些? 企业庆典活动主要包括哪些类型 浙江福斯特 充气蹦床厂家及价格举例 竣工仪式的活动方案 什么叫pvc夹网充气船
    声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com