zoukankan      html  css  js  c++  java
  • 侯策《前端开发核心知识进阶》读书笔记——React组件设计

    组件的单一职责

    原则上讲,组件只应该做一件事情。但是对于应用来说,全部组件都拆散,只有单一职责并没有必要,反而增加了编写的繁琐程度。那什么时候需要拆分组件,保证单一职责呢?如果一个功能集合有可能发生变化,那么就需要最大程度地保证单一职责。

    单一职责带来的最大好处就是在修改组件时,能够做到全在掌控下,不必担心对其他组件造成影响。举个例子:我们的组件需要通过网络请求获取数据并展示数据内容,这样一来潜在的功能集合改变就有:

    • 请求 API 地址发生变化
    • 请求返回数据格式变化
    • 开发者想更换网络请求第三方库,比如 jQuery.ajax 改成 axios
    • 更改请求数据逻辑

    再看一个例子:我们需要一个 table 组件,渲染一个 list,那么潜在更改的可能有:

    • 限制一次性渲染的 item 个数(只渲染前 10 个,剩下的懒加载)
    • 当数据列表为空时显示 “This list is empty”
    • 任何渲染逻辑的更改

    实际看一个场景

    import axios from 'axios'
    
    class Weather extends Component {  
      constructor(props) {
        super(props)
        this.state = { temperature: 'N/A', windSpeed: 'N/A' }
      }
    
      componentDidMount() {
        axios.get('http://weather.com/api').then(response => {
          const { current } = response.data
          this.setState({
            temperature: current.temperature,
            windSpeed: current.windSpeed
          })
        })
      }
    
      render() {
        const { temperature, windSpeed } = this.state
        return (
          <div className="weather">
            <div>Temperature: {temperature} °C</div>
            <div>Wind: {windSpeed} km/h</div>
          </div>
        )
      }
    }

    这个组件很容易理解,并且看上去没什么大问题,但是并不符合单一职责。比如这个 Weather 组件将数据获取与渲染逻辑耦合在一起,如果数据请求有变化,就需要在 componentDidMount 生命周期中进行改动;如果展示天气的逻辑有变化,render 方法又需要变动。

    如果我们将这个组件拆分成:WeatherFetch 和 WeatherInfo 两个组件,这两个组件各自只做一件事情,保持单一职责:

    import axios from 'axios'
    import WeatherInfo from './weatherInfo'
    
    class WeatherFetch extends Component {  
      constructor(props) {
        super(props)
        this.state = { temperature: 'N/A', windSpeed: 'N/A' }
      }
    
      componentDidMount() {
        axios.get('http://weather.com/api').then(response => {
          const { current } = response.data
          this.setState({
            temperature: current.temperature,
            windSpeed: current.windSpeed
            })
          })
      }
    
      render() {
        const { temperature, windSpeed } = this.state
        return (
          <WeatherInfo temperature={temperature} windSpeed={windSpeed} />
        )
      }
    }

    在另外一个文件中:

    const WeatherInfo = ({ temperature, windSpeed }) => 
      (
        <div className="weather">
          <div>Temperature: {temperature} °C</div>
          <div>Wind: {windSpeed} km/h</div>
        </div>
      )

    如果我们想进行重构,使用 async/await 代替 Promise,只需要直接更改 WeatherFetch 组件:

    class WeatherFetch extends Component {  
      // ...
    
      async componentDidMount() {
        const response = await axios.get('http://weather.com/api')
        const { current } = response.data
    
        this.setState({
          temperature: current.temperature,
          windSpeed: current.windSpeed
          })
        })
      }
    
      // ...
    }

    这只是一个简单的例子,在真实项目中,保持组件的单一职责将会非常重要,甚至我们可以使用 HoC 强制组件的单一职责性。

    组件通信和封装

    组件关联有紧耦合和松耦合之分,众所周知,松耦合带来的好处是很直接的:

    • 一处组件的改动完全独立,不影响其他组件
    • 更好的复用设计
    • 更好的可测试性

    场景:简单计数器

    class App extends Component {  
      constructor(props) {
        super(props)
        this.state = { number: 0 }
      }
    
      render() {
        return (
          <div className="app"> 
            <span className="number">{this.state.number}</span>
            <Controls parent={this} />
          </div>
        )
      }
    }
    
    class Controls extends Component {
      updateNumber(toAdd) {
        this.props.parent.setState(prevState => ({
          number: prevState.number + toAdd       
        }))
      }
    
      render() {
        return (
          <div className="controls">
            <button onClick={() => this.updateNumber(+1)}>
              Increase
            </button> 
            <button onClick={() => this.updateNumber(-1)}>
              Decrease
            </button>
          </div>
        )
      }
    }

    这样的组件实现问题很明显:App 组件不具有封装性,它将实例传给 Controls 组件,Controls 组件可以直接更改 App state 的内容。

    优化后的代码

    class App extends Component {  
      constructor(props) {
        super(props)
        this.state = { number: 0 }
      }
    
      updateNumber(toAdd) {
        this.setState(prevState => ({
          number: prevState.number + toAdd       
        }))
      }
    
      render() {
        return (
          <div className="app"> 
            <span className="number">{this.state.number}</span>
            <Controls 
              onIncrease={() => this.updateNumber(+1)}
              onDecrease={() => this.updateNumber(-1)} 
            />
          </div>
        )
      }
    }
    
    
    const Controls = ({ onIncrease, onDecrease }) => {  
      return (
        <div className="controls">
          <button onClick={onIncrease}>Increase</button> 
          <button onClick={onDecrease}>Decrease</button>
        </div>
      )
    }

    这样一来,Controls 组件就不需要再知道 App 组件的内部情况,实现了更好的复用性和可测试性,App 组件因此也具有了更好的封装性。

    组合性

    如果两个组件 Composed1 和 Composed2 具有相同的逻辑,我们可以使用组合性进行拆分重组:

    const instance1 = (  
      <Composed1>
          // Composed1 逻辑
          // 重复逻辑
      </Composed1>
    )
    const instance2 = (  
      <Composed2>
          // 重复逻辑
          // Composed2 逻辑
      </Composed2>
    )

    重复逻辑提取为common组件

    const instance1 = (  
      <Composed1>
        <Logic1 />
        <Common />
      </Composed1>
    )
    const instance2 = (  
      <Composed2>
        <Common />
        <Logic2 />
      </Composed2>
    )

    副作用和(准)纯组件

    组件可测试性

    组件命名

    (待完善)

  • 相关阅读:
    关键两招就解决Wampserver 打开localhost显示IIS7图片问题
    死磕!Windows下Apache+PHP+phpmyadmin的配置
    宝塔linux面板运行jsp文件的配置工作
    Python关于self用法重点分析
    Atom窗口切换和放大或者缩小
    容易掉坑的地方The value for the useBean class attribute XXX is invalid
    JS 之如何在插入元素时插在原有元素的前面而不是末尾
    ul或者ol中添加li元素
    页面右下角广告
    getAttribute与setAttribute
  • 原文地址:https://www.cnblogs.com/fmyao/p/12827103.html
Copyright © 2011-2022 走看看