刚开始学习react的时候,看到了各种各样的组件编写方式,不同教程中提出的方法往往大相径庭。虽然当时react这个框架已经很成熟了,但是好像还没有一个公认的正确的使用方式。这几年,我们团队写了很多react组件,我们也在不断优化实现方式,直到我们满意为止。本文介绍了我们在实践中的最佳实践,希望对初学者和有经验的开发者都有一些帮助。在我们开始之前,有几点需要澄清:我们使用的是es6和es7语法如果您不了解展示组件和容器组件之间的区别,您可以先阅读这篇文章如果您有任何建议、问题或反馈,你可以给我们留言ClassBasedComponents(基于类的组件)基于类的组件有自己的状态和方法。我们会尽可能谨慎地使用这些组件,但它们都有自己的使用场景。接下来我们将逐行编写组件。importCSSimportReact,{Component}from'react'import{observer}from'mobx-react'importExpandableFormfrom'./ExpandableForm'import'./styles/ProfileContainer.css'我喜欢JS中的CSS,但它仍然是一种新的想法,尚未产生成熟的解决方案。我们已经在每个组件中导入了它的css文件。译者注:目前JS中的CSS可以使用cssmodules方案来解决。webpack的css-loader已经提供了这个功能。我们还使用空行来区分我们的依赖项。译者注:即第4行和第5行与第1行和第2行之间会加一个单独的空行。初始化stateimportReact,{Component}from'react'import{observer}from'mobx-react'importExpandableFormfrom'./ExpandableForm'import'./styles/ProfileContainer.css'exportdefaultclassProfileContainerextendsComponent{state={expanded:false}你也可以在构造函数中在中初始化状态,但我们更喜欢这种简洁的方式。我们还将确保默认导出组件的类。propTypes和defaultPropsimportReact,{Component}from'react'import{observer}from'mobx-react'import{string,object}from'prop-types'importExpandableFormfrom'./ExpandableForm'import'./styles/ProfileContainer.css'exportdefaultclassProfileContainerextendsComponent{state={expanded:false}staticpropTypes={model:object.isRequired,title:string}staticdefaultProps={model:{id:0},title:'YourName'}propTypes和defaultProps是静态属性,应该在代码中尽可能多的top语句。这两个属性充当文档,阅读代码的开发人员应该立即可见。如果您使用的是React15.3.0或更高版本,请使用prop-types而不是React.PropTypes。你所有的组件都应该有propTypes属性。方法importReact,{Component}from'react'import{observer}from'mobx-react'import{string,object}from'prop-types'importExpandableFormfrom'./ExpandableForm'import'./styles/ProfileContainer.css'exportdefaultclassProfileContainerextendsComponent{state={expanded:false}staticpropTypes={model:object.isRequired,title:string}staticdefaultProps={model:{id:0},title:'YourName'}handleSubmit=(e)=>{e.preventDefault()this.props.model.save()}handleNameChange=(e)=>{this.props.model.changeName(e.target.value)}handleExpand=(e)=>{e.preventDefault()this.setState({expanded:!this.state.expanded})}使用类组件,当你向子组件传递方法时,你需要确保这些方法在调用时具有正确的this值。通常在传递给子组件时使用this.handleSubmit.bind(this)。当然使用es6的箭头函数更简洁。译者注:方法的上下文绑定也可以在构造函数中完成:constructor(){this.handleSubmit=this.handleSubmit.bind(this);}中传递一个函数给setState作为参数(passingsetStateaFunction)在上面的例子中,我们是这样做的:this.setState({expanded:!this.state.expanded})setState实际上是异步执行的,react出于性能考虑会将状态变化整合在一起处理,所以当调用setState时,状态不一定立即改变。这意味着在调用setState时您不能依赖当前状态值——因为您无法确定实际调用setState时的状态。解决方案是将一个方法传递给setState,该方法接收最后一个状态作为参数。this.setState(prevState=>({expanded:!prevState.expanded})解析propsimportReact,{Component}from'react'import{observer}from'mobx-react'import{string,object}from'prop-types'importExpandableFormfrom'./ExpandableForm'import'./styles/ProfileContainer.css'exportdefaultclassProfileContainerextendsComponent{state={expanded:false}staticpropTypes={model:object.isRequired,title:string}staticdefaultProps={model:{id:0},title:'你的名字'}handleSubmit=(e)=>{e.preventDefault()this.props.model.save()}handleNameChange=(e)=>{this.props.model.changeName(e.target.value)}handleExpand=(e)=>{e.preventDefault()this.setState(prevState=>({expanded:!prevState.expanded}))}render(){const{model,title}=this.propsreturn({title}
)}}对于有很多props的组件,每个属性都要像上面那样解构,每个属性都有单独一行装饰器(Decorators)@observerexportdefaultclassProfileContainerextendsComponent{如果你使用的是state像mobx这样的管理器,你可以像上面那样描述你的组件。这种写法和将组件作为参数传递给函数是一样的。装饰器是一种非常灵活且易于阅读的定义组件功能的方式。我们使用mobx和mobx-models来组合装饰器,如果你不想使用装饰器,你可以这样做:classProfileContainerextendsComponent{//Componentcode}exportdefaultobserver(ProfileContainer)closurevalue={model.name}//onChange={(e)=>{model.name=e.target.value}}//^不要这样写,如下:onChange={this.handleChange}placeholder="YourName"/>原因是每次父组件重新渲染s,将创建一个新函数并将其传递给输入。如果输入是一个React组件,这将导致组件重新渲染,而不管组件的其他属性是否发生变化。而且,传入父组件的方法的方式也会让代码的可读性更强,更容易调试,也更容易修改。完整代码如下:importReact,{Component}from'react'import{observer}from'mobx-react'import{string,object}from'prop-types'//SeparatelocalimportsfromdependenciesimportExpandableFormfrom'./ExpandableForm'import'./styles/ProfileContainer.css'//Usedecoratorsifneeded@observerexportdefaultclassProfileContainerextendsComponent{state={expanded:false}//在这里初始化状态(ES7)orinaconstructormethod(ES6)//DeclarepropTypesasstaticpropertiesasearlyaspossiblestaticpropTypes={model:object.isRequired,title:string}//DefaultpropsbelowpropTypesstaticdefaultProps={model:{id:0},title:'YourName'}//使用fatarrowfunctionsformethodstopreservecontext(thiswillthusbethecomponentinstance)handleSubmit=(e)=>{e.preventDefault()this.props.model.save()}handleNameChange=(e)=>{this.props.model.name=e.target.value}handleExpand=(e)=>{e.preventDefault()this.setState(prevState=>({expanded:!prevState.expanded}))}render(){//Destructurepropsforreadabilityconst{model,title}=this.propsreturn(//Newlinepropsiftherearemorethantwo{title}
{model.name=e.target.value}}//避免创建newclosingintherendermethod-usemethodslikebelowonChange={this.handleNameChange}placeholder="YourName"/>/ExpandableForm>)}}函数式组件(FunctionalComponents)这些组件没有状态和方法,它们是纯粹的,非常容易定位问题,你可以尽可能多地使用这些组件。propTypesimportReactfrom'react'import{observer}from'mobx-react'import{func,bool}from'prop-types'import'./styles/Form.css'ExpandableForm.propTypes={onSubmit:func.isRequired,expanded:bool}//Componentdeclaration这里我们在组件声明之前定义了propTypes,非常直观。我们之所以可以这样做,是因为js的函数名提升机制。DestructuringPropsanddefaultProps(解构道具和defaultProps)importReactfrom'react'import{observer}from'mobx-react'import{func,bool}from'prop-types'import'./styles/Form.css'ExpandableForm.propTypes={onSubmit:func.isRequired,expanded:bool,onExpand:func.isRequired}functionExpandableForm(props){constformStyle=props.expanded?{height:'auto'}:{height:0}return({props.children}Expand)}我们的组件是一个函数,props作为函数的入参传入。我们可以这样扩展组件::func.isRequired,expanded:bool,onExpand:func.isRequired}functionExpandableForm({onExpand,expanded=false,children,onSubmit}){constformStyle=expanded?{height:'auto'}:{height:0}return({children}Expand)}我们可以给参数设置默认值,如defaultProps。如果expanded未定义,则将其设置为false。(这种设置默认值的方式对于对象类的输入非常有用,可以避免`can'treadpropertyXXXXofundefined的错误)不要使用es6的箭头函数:constExpandableForm=({onExpand,expanded,children})=>{这样写,函数其实就是一个匿名函数。如果正确使用babel,是没有问题的,但是如果没有正确使用,就会在运行时造成一些错误,非常不方便调试。此外,在React的测试库Jest中使用匿名函数可能会导致问题。由于使用匿名函数的一些潜在问题,我们建议使用function而不是const。包装不能在函数组件中使用装饰器,我们可以将其作为输入参数传递给观察者函数./styles/Form.css'ExpandableForm.propTypes={onSubmit:func.isRequired,expanded:bool,onExpand:func.isRequired}functionExpandableForm({onExpand,expanded=false,children,onSubmit}){constformStyle=expanded?{height:'auto'}:{height:0}return({children}Expand)}exportdefaultobserver(ExpandableForm)完成组件看起来像这样:importReactfrom'react'import{observer}from'mobx-react'import{func,bool}from'prop-types'//Separatelocalimportsfromdependenciesimport'./styles/Form.css'//DeclarepropTypeshere,beforethecomponent(takingadvantageofJSfunctionhoisting)//你想让这些尽可能可见ExpandableForm.propTypes={onSubmit:func.isRequired,expanded:bool,onExpand:func.isRequired}//像这样解构props,并使用默认参数asawayofsettingdefaultPropsfunctionExpandableForm({onExpand,expanded=false,children,onSubmit}){constformStyle=expanded?{height:'auto'}:{height:0}return({children}Expand)}//Wrapthecomponentinsteadofdecoratingitexportdefaultobserver(ExpandableForm)在JSX中使用条件判断(ConditionalsinJSX)有时候我们需要在render中写很多判断逻辑,下面的写法方法是我们应该避免的:目前有一些库可以解决这个问题,但是我们没有引入其他依赖,而是采用了下面的方法来解决问题:这里我们通过立即执行函数来解决问题,把ifstatementin在立即执行函数中,想返回什么就返回什么。需要注意的是,立即执行功能会带来一定的性能问题,但是为了代码的可读性,这种影响可以忽略。此外,当您只想在特定条件下呈现时,不要执行:{isTrue?True!
:}而是:{isTrue&&True!
}(完整文本)