React Cookbook

组件声明

使用ES6 class声明,如果有propTypes则必须写在顶部,lifecycle events必须写在一起

class
  propTypes
  defaultPropTypes
  constructor
    event handlers
  getters
  render
class Person extends React.Component {
  // 如果有propTypes则必须写在顶部
  state propTypes = {
    firstName: PropTypes.string.isRequired,
    lastName: PropTypes.string.isRequired,
  }  

  constructor(props) {
      super(props)
      this.state = { smiling : false }
  }

  // lifecycle events必须写在一起
  componentWillMount() {

  }

  componentDidMount() {

  }

  handleClick = () => {

  }

  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.aghe > 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开头
  • 以事件类型作为结尾(eg: Click, Change)
  • 使用一般现在时
// bad
closeAll = () => {}

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

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

组件化优于多层render

当组件的jsx只写在一个render方法会比较臃肿,更适合拆分成一个组件
视情况采用class component或stateless component
★ render =》 以更大的可能实现组件化开发

// 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 => <Item {...item}/>)}
      </ul>
    </div>
  )
}

状态上移优于公共方法

一般组件不应该提供公共方法,会破坏数据流只有一个方向的原则
为了倾向于更细颗粒的组件化,状态应该集中在远离渲染的地方处理(eg: 应用级别的状态就在redux的store中)能够更加方便兄弟组件共享

// bad
class DropDownMun 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'>
        // 1234
      </div>
    )
  }
}

// 不好的地方在于: 父组件通过调用子组件来控制子组件的状态数据
// 比较好的方式是: 父组件通过调用自身的方法来控制状态数据并将状态数据传递到子组件中,由子组件根据状态数据
// 进行选择性的渲染
class MyComponent extends Component {
    showMenu() {
      this.refs.menu.show();
    }
    hideMenu() {
      this.refs.menu.hide();
    }
    render() {
      return <DropDownMenu ref='menu'/>
    }
}

// good

// 父组件:状态数据控制逻辑

class MyComponent extends Component {
  constructor(props) {
    super(props)
  
    // 定义状态数据
    this.state = {
        showMenu: flase
    }
  }

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

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

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

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

容器组件

一个容器组件主要负责维护状态和数据的计算,本身没有界面逻辑,只有把结果通过props传递下去。
区分容器组件的目的就是可以把组件的状态和渲染解耦开来,改写界面的时候可以不用关注数据的实现,以得到可复用性。

// bad  将状态数据的处理逻辑和状态数据的渲染逻辑融合到了一起   违犯高内聚低耦合的特性 无法提高模块的可复用性和可维护性
class MessageList extends Component {
  constructor(props) {
    super(props)
    this.state = {
      onlyUnread: false,
      message: []
    }
  }

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

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 =》 容器组件:创建状态数据+状态数据逻辑处理 ||| 渲染组件:状态数据渲染逻辑

// 容器组件 <MessageContainer>
class MessageContainer extends Component {
  constructor(props) {
    super(props) {
      this.state = {
        onlyUnread: false,
        message: []
      }
    }
  }
  
  componentDidMount() {
     $.ajax({
        url: '/api/messages',
    }).then(({message}) => this.setState({message}));
  }
  
  handleClick = () => this.setState({onlyUnread: !this.state.onlyUnread});

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

// 控制状态数据渲染逻辑的 MessageList(直接用函数式组件)
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 = {
  message: 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>
}

始终声明PropTypes

// http://facebook.github.io/react/docs/reusable-components.html#prop-validation

Props的非空检测

对于并非isRequired的protype,必须对应设置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',
  }
}

使用Props初始化

除非props的命名明确指出了意图,否则不应该使用props来初始化state
一般来说不会使用Props对state进行数据初始化,因为该种方式是Anti-Pattern的

* bad
constructor(props) {
  this.state = {
    items: props.items
  }
}

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

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)}/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
}

posted @ 2023-10-05 17:04  Felix_Openmind  阅读(20)  评论(0)    收藏  举报
*{cursor: url(https://files-cdn.cnblogs.com/files/morango/fish-cursor.ico),auto;}