All Projects → shimohq → React Cookbook

shimohq / React Cookbook

Licence: mit
编写简洁漂亮,可维护的 React 应用

Projects that are alternatives of or similar to React Cookbook

Date Time Picker
Angular Date Time Picker (Responsive Design)
Stars: ✭ 528 (-11.11%)
Mutual labels:  component
React Range
🎚️Range input with a slider. Accessible. Bring your own styles and markup.
Stars: ✭ 545 (-8.25%)
Mutual labels:  component
Cp Ddd Framework
A lightweight flexible development framework for complex business architecture with full ecosystem!轻量级业务中台开发框架,中台架构的顶层设计和完整解决方案!
Stars: ✭ 566 (-4.71%)
Mutual labels:  clean-code
Ngx Ui
🚀 Style and Component Library for Angular
Stars: ✭ 534 (-10.1%)
Mutual labels:  component
Nouislider
noUiSlider is a lightweight, ARIA-accessible JavaScript range slider with multi-touch and keyboard support. It is fully GPU animated: no reflows, so it is fast; even on older devices. It also fits wonderfully in responsive designs and has no dependencies.
Stars: ✭ 5,127 (+763.13%)
Mutual labels:  component
Ramda Adjunct
Ramda Adjunct is the most popular and most comprehensive set of functional utilities for use with Ramda, providing a variety of useful, well tested functions with excellent documentation.
Stars: ✭ 550 (-7.41%)
Mutual labels:  cookbook
Miniprogram Demo
微信小程序组件 / API / 云开发示例
Stars: ✭ 5,207 (+776.6%)
Mutual labels:  component
Javascript Airbnb
Перевод «JavaScript Style Guide» от Airbnb
Stars: ✭ 579 (-2.53%)
Mutual labels:  styleguide
Vuesax
New Framework Components for Vue.js 2
Stars: ✭ 5,293 (+791.08%)
Mutual labels:  component
Vue Dplayer
📹 A Vue 2.x video player component based on DPlayer
Stars: ✭ 565 (-4.88%)
Mutual labels:  component
Folding Cell Android
📃 FoldingCell is a material design expanding content cell inspired by folding paper material made by @Ramotion
Stars: ✭ 4,859 (+718.01%)
Mutual labels:  component
Pep8speaks
A GitHub app to automatically review Python code style over Pull Requests
Stars: ✭ 546 (-8.08%)
Mutual labels:  styleguide
React Markdown Editor Lite
a light-weight Markdown editor based on React. 一款轻量的基于React的markdown编辑器
Stars: ✭ 553 (-6.9%)
Mutual labels:  component
Vue Plyr
A Vue component for the plyr (https://github.com/sampotts/plyr) video & audio player.
Stars: ✭ 531 (-10.61%)
Mutual labels:  component
React Laag
Hooks to build things like tooltips, dropdown menu's and popovers in React
Stars: ✭ 568 (-4.38%)
Mutual labels:  component
Git Style Guide
A Git Style Guide
Stars: ✭ 4,851 (+716.67%)
Mutual labels:  styleguide
Vue Img Inputer
🏞 A graceful image type inputer / uploader
Stars: ✭ 548 (-7.74%)
Mutual labels:  component
Vue Table Component
A straight to the point Vue component to display tables
Stars: ✭ 592 (-0.34%)
Mutual labels:  component
React Scrollbars Custom
The best React custom scrollbars component
Stars: ✭ 576 (-3.03%)
Mutual labels:  component
Google Map React
Google map library for react that allows rendering components as markers 🎉
Stars: ✭ 5,529 (+830.81%)
Mutual labels:  component

React Cookbook

编写简洁漂亮,可维护的 React 应用

目录


前言

随着应用规模和维护人数的增加,光靠 React 本身灵活易用的 API 并不足以有效控制应用的复杂度。本指南旨在在 ESLint 之外,再建立一个我们团队内较为一致认可的约定,以增加代码一致性和可读性、降低维护成本。

欢迎在 Issues 进行相关讨论

组件声明

全面使用 ES6 class 声明,可不严格遵守该属性声明次序,但如有 propTypes 则必须写在顶部, lifecycle events 必须写到一起。

  • class
    • propTypes
    • defaultPropTypes
    • constructor
      • event handlers (如不使用类属性语法可在此声明)
    • lifecycle events
    • event handlers
    • getters
    • render
class Person extends React.Component {
  static propTypes = {
    firstName: PropTypes.string.isRequired,
    lastName: PropTypes.string.isRequired
  }
  constructor (props) {
    super(props)

    this.state = { smiling: false }

    /* 若不能使用 babel-plugin-transform-class-properties
    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling})
    }
    */
  }

  componentWillMount () {}

  componentDidMount () {}

  // ...

  handleClick = () => {
    this.setState({smiling: !this.state.smiling})
  }

  get fullName () {
    return this.props.firstName + this.props.lastName
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.fullName} {this.state.smiling ? 'is smiling.' : ''}
      </div>
    )
  }
}

⬆ 回到目录

计算属性

使用 getters 封装 render 所需要的状态或条件的组合

对于返回 boolean 的 getter 使用 is- 前缀命名

  // bad
  render () {
    return (
      <div>
        {
          this.state.age > 18
            && (this.props.school === 'A'
              || this.props.school === 'B')
            ? <VipComponent />
            : <NormalComponent />
        }
      </div>
    )
  }

  // good
  get isVIP() {
    return
      this.state.age > 18
        && (this.props.school === 'A'
          || this.props.school === 'B')
  }
  render() {
    return (
      <div>
        {this.isVIP ? <VipComponent /> : <NormalComponent />}
      </div>
    )
  }

⬆ 回到目录

事件回调命名

Handler 命名风格:

  • 使用 handle 开头
  • 以事件类型作为结尾 (如 Click, Change)
  • 使用一般现在时
// bad
closeAll = () => {},

render () {
  return <div onClick={this.closeAll} />
}
// good
handleClick = () => {},

render () {
  return <div onClick={this.handleClick} />
}

如果你需要区分同样事件类型的 handler(如 handleNameChangehandleEmailChange)时,可能这就是一个拆分组件的信号

⬆ 回到目录

组件化优于多层 render

当组件的 jsx 只写在一个 render 方法显得太臃肿时,很可能更适合拆分出一个组件,视情况采用 class component 或 stateless component

// bad
renderItem ({name}) {
  return (
    <li>
    	{name}
    	{/* ... */}
    </li>
  )
}

render () {
  return (
    <div className="menu">
      <ul>
        {this.props.items.map(item => this.renderItem(item))}
      </ul>
    </div>
  )
}
// good
function Items ({name}) {
  return (
    <li>
    	{name}
    	{/* ... */}
    </li>
  )
}

render () {
  return (
    <div className="menu">
      <ul>
        {this.props.items.map(item => <Items {...item} />)}
      </ul>
    </div>
  )
}

⬆ 回到目录

状态上移优于公共方法

一般组件不应提供公共方法,这样会破坏数据流只有一个方向的原则。

再因为我们倾向于更细颗粒的组件化,状态应集中在远离渲染的地方处理(比如应用级别的状态就在 redux 的 store 里),也能使兄弟组件更方便地共享。

//bad
class DropDownMenu extends Component {
  constructor (props) {
    super(props)
    this.state = {
      showMenu: false
    }
  }

  show () {
    this.setState({display: true})
  }

  hide () {
    this.setState({display: false})
  }

  render () {
    return this.state.display && (
      <div className="dropdown-menu">
        {/* ... */}
      </div>
    )
  }
}

class MyComponent extends Component {
  // ...
  showMenu () {
    this.refs.menu.show()
  }
  hideMenu () {
    this.refs.menu.hide()
  }
  render () {
    return <DropDownMenu ref="menu" />
  }
}

//good
class DropDownMenu extends Component {
  static propsType = {
    display: PropTypes.boolean.isRequired
  }

  render () {
    return this.props.display && (
      <div className="dropdown-menu">
        {/* ... */}
      </div>
    )
  }
}

class MyComponent extends Component {
  constructor (props) {
    super(props)
    this.state = {
      showMenu: false
    }
  }

  // ...

  showMenu () {
    this.setState({showMenu: true})
  }

  hideMenu () {
    this.setState({showMenu: false})
  }

  render () {
    return <DropDownMenu display={this.state.showMenu} />
  }
}

更多阅读: lifting-state-up

容器组件

一个容器组件主要负责维护状态和数据的计算,本身并没有界面逻辑,只把结果通过 props 传递下去。

区分容器组件的目的就是可以把组件的状态和渲染解耦开来,改写界面时可不用关注数据的实现,顺便得到了可复用性。

// bad
class MessageList extends Component {
  constructor (props) {
    super(props)
  	this.state = {
        onlyUnread: false,
        messages: []
  	}
  }

  componentDidMount () {
    $.ajax({
      url: "/api/messages",
    }).then(({messages}) => this.setState({messages}))
  }

  handleClick = () => this.setState({onlyUnread: !this.state.onlyUnread})

  render () {
    return (
      <div class="message">
        <ul>
          {
            this.state.messages
              .filter(msg => this.state.onlyUnread ? !msg.asRead : true)
              .map(({content, author}) => {
                return <li>{content}{author}</li>
              })
          }
        </ul>
        <button onClick={this.handleClick}>toggle unread</button>
      </div>
    )
  }
}
// good
class MessageContainer extends Component {
  constructor (props) {
    super(props)
  	this.state = {
        onlyUnread: false,
        messages: []
  	}
  }

  componentDidMount () {
    $.ajax({
      url: "/api/messages",
    }).then(({messages}) => this.setState({messages}))
  }

  handleClick = () => this.setState({onlyUnread: !this.state.onlyUnread})

  render () {
    return <MessageList
      messages={this.state.messages.filter(msg => this.state.onlyUnread ? !msg.asRead : true)}
      toggleUnread={this.handleClick}
    />
  }
}

function MessageList ({messages, toggleUnread}) {
  return (
    <div class="message">
      <ul>
        {
          messages
            .map(({content, author}) => {
              return <li>{content}{author}</li>
            })
        }
      </ul>
      <button onClick={toggleUnread}>toggle unread</button>
    </div>
  )
}
MessageList.propTypes = {
  messages: propTypes.array.isRequired,
  toggleUnread: propTypes.func.isRequired
}

更多阅读:

⬆ 回到目录

纯函数的 render

render 函数应该是一个纯函数(stateless component 当然也是),不依赖 this.state、this.props 以外的变量,也不改变外部状态

// bad
render () {
  return <div>{window.navigator.userAgent}</div>
}

// good
render () {
  return <div>{this.props.userAgent}</div>
}

更多阅读: Return as soon as you know the answer

⬆ 回到目录

始终声明 PropTypes

每一个组件都声明 PropTypes,非必须的 props 应提供默认值。

对于非常广为人知的 props 如 children, dispatch 也不应该忽略。因为如果一个组件没有声明 dispatch 的 props,那么一眼就可以知道该组件没有修改 store 了。

但如果在开发一系列会 dispatch 的组件时,可在这些组件的目录建立单独的 .eslintrc 来只忽略 dispatch。

更多阅读: Prop Validation

⬆ 回到目录

Props 非空检测

对于并非 isRequired 的 proptype,必须对应设置 defaultProps,避免再增加 if 分支带来的负担

// bad
render () {
  if (this.props.person) {
    return <div>{this.props.person.firstName}</div>
  } else {
    return <div>Guest</div>
  }
}
// good
class MyComponent extends Component {
  render() {
    return <div>{this.props.person.firstName}</div>
  }
}

MyComponent.defaultProps = {
  person: {
    firstName: 'Guest'
  }
}

如有必要,使用 PropTypes.shape 明确指定需要的属性

⬆ 回到目录

使用 Props 初始化

除非 props 的命名明确指出了意图,否则不该使用 props 来初始化 state

// bad
constructor (props) {
  this.state = {
    items: props.items
  }
}
// good
constructor (props) {
  this.state = {
    items: props.initialItems
  }
}

更多阅读: "Props in getInitialState Is an Anti-Pattern"

⬆ 回到目录

classnames

使用 classNames 来组合条件结果.

// bad
render () {
  return <div className={'menu ' + this.props.display ? 'active' : ''} />
}
// good
render () {
  const classes = {
    menu: true,
    active: this.props.display
  }

  return <div className={classnames(classes)} />
}

Read: Class Name Manipulation

⬆ 回到目录

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].