• Stars
    star
    625
  • Rank 69,744 (Top 2 %)
  • Language
  • License
    MIT License
  • Created over 7 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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

⬆ 回到目录

More Repositories

1

chinese-programmer-wrong-pronunciation

中国程序员容易发音错误的单词
JavaScript
21,540
star
2

react-native-prompt-android

A polyfill library for Alert.prompt on Android platform, working both on Android and iOS platform.
Java
129
star
3

shimo-navigation

石墨 react-native app 导航系统
JavaScript
125
star
4

react-native-cookie

A cookie manager module for react-native
Objective-C
92
star
5

ioredis-tree

🌲 A robust tree structure implementation for Redis
JavaScript
84
star
6

sdk-cabinet

石墨 SDK 简易开发
TypeScript
54
star
7

shimo.js

[WIP] Official Shimo client for Node.js
JavaScript
42
star
8

finch

🤖 Puppeteer as a Service
TypeScript
31
star
9

react-native-preference

Manage react-native app's preference data synchronously
Java
23
star
10

bay

The framework
JavaScript
20
star
11

koa-yield-breakpoint

Add breakpoints around `yield` expression especially for koa@1.
JavaScript
17
star
12

Redoctor

Redis doctor for understanding how the memory is used in your Redis server
JavaScript
16
star
13

wormalize

Normalizes nested JSON according to a schema
JavaScript
15
star
14

awos

AWOS: Wrapper For Aliyun OSS And Amazon S3 SDK
Go
15
star
15

awos-js

AWOS-JS: Wrapper For Aliyun OSS And Amazon S3
TypeScript
14
star
16

yato

A node module similar to hystrix. Who caused riots - cut it!
TypeScript
12
star
17

generator2async-codemod

JavaScript
10
star
18

sdk-cabinet-example

石墨 SDK 简易开发演示项目
CSS
9
star
19

redis-messenger

Insanely Fast Communication Library For Node.js Services Using Redis
JavaScript
6
star
20

tomqueue

A FIFO queue with group-level concurrency support
JavaScript
6
star
21

shimo-js-sdk

TypeScript
6
star
22

fc-toolkit

Toolkit for aliyun function compute(https://cn.aliyun.com/product/fc)
TypeScript
5
star
23

docker-tools

Docker tools used by shimo
JavaScript
4
star
24

mocha2ava-codemod

codemod for mocha tests
JavaScript
4
star
25

hequelize

Simple HBase ORM based on hbase-rpc-client
JavaScript
4
star
26

resolve-keypath

Resolve the value of an object according the keypath
JavaScript
3
star
27

react-native-navigators

Native navigators system for react-native
Objective-C
3
star
28

BisonView

WebView for Android
C++
3
star
29

redis-scan

Scan redis keys with pattern and do something to them
JavaScript
3
star
30

china-divisions

中国行政区划查询服务
Go
3
star
31

opentracing-auto

Out of the box distributed tracing for Node.js applications with OpenTracing.
JavaScript
3
star
32

hbase-rest

HBase REST client.
JavaScript
3
star
33

js-type-convert

convert js many types without stackoverflow!
JavaScript
2
star
34

native-ios-sdk

Objective-C
2
star
35

kibana-requestId-link

JavaScript
2
star
36

shimo-extension

石墨文档离线标签页扩展
2
star
37

room-client

room & client class stored with redis or memory
JavaScript
1
star
38

node-mmmagic-type

Detect file type with mmmagic and mime.
TypeScript
1
star
39

eagle

server status check tool
JavaScript
1
star
40

co-yield-breakpoint

Add breakpoints around `yield` expression.
JavaScript
1
star
41

update-package-version

Update your package version in package.json and git commit it automatically
JavaScript
1
star
42

guid

A Guid generator and validator.
JavaScript
1
star
43

request

node.js http request library based on request-promise
JavaScript
1
star
44

fixed-guid

Fixed guid generator based on node.js & redis
JavaScript
1
star
45

qrcode

qrcode web service
JavaScript
1
star
46

go-url-join

Like `path.Join()` but for a URL.
Go
1
star