React組件拆分之道

爲何要拆分組件

提升可讀性、可維護性javascript

若是不拆分

  • 代碼量大,全部內容集中在一塊兒
  • 相同組件沒法複用
  • 業務開發分工不明確,開發人員要關心非業務的代碼
  • 改代碼時,可能會影響其餘業務,牽一髮動全身(耦合)
  • 任何一個操做都致使整個應用從新render

目標

  • 架構清晰
  • 相同組件可以複用
  • 業務分工明確,開發人員僅專一與本身的業務
  • 每一個組件負責獨立的功能,與其餘組件解耦合
  • 可以使用SCU、memo減小沒必要要渲染

如何拆分組件

把相關聯的東西放一塊兒(按功能、業務)java

  • 橫向(按業務、功能模塊劃分)
  • 縱向(應用、系統層級劃分)

一個React組件的功能

  • 維護局部數據: state、ref、後臺返回等
  • 獲取、修改全局數據
  • 事件處理、數據監聽處理(useEffect/componentDidUpdate等)
  • IO: 網絡請求/本地讀寫
  • 數據處理
  • render

組件分類

展現組件

只有render方法、簡單的交互事件處理和state管理。好比Input/CheckBox等。redux

劃分標準: 根據UI稿,不一樣的展現模塊分爲不一樣的組件。好比頂部、底部、導航、列表等網絡

容器組件

業務組件

與數據源(redux/後臺/本地存儲)進行數據傳輸操做(不止是IO)架構

劃分標準: 根據業務功能劃分。好比登陸、登出、支付、表單校驗等函數

鏈接組件

鏈接業務組件和展現組件, 主要用於處理數據後傳給展現組件。優化

組件樹結構

展現組件內能夠有容器組件,容器組件內也能夠有展現組件this

React組件樹

案例

邏輯、展現分離

把渲染和功能拆分紅不一樣組件,提升複用性spa

不拆分

登陸組件處理了2件事情:code

  1. 渲染登陸表單
  2. 記錄用戶輸入和登陸狀態,向後臺發送登陸請求
class Login extends Component {
  constructor(props) {
    super(props)

    this.state = {
      account: '',
      password: '',
      status: 'init',
    }
  }

  handleAccountChange(e) {
    this.setState({account: e.target.value})
  }

  handlePasswordChange(e) {
    this.setState({password: e.target.value})
  }

  handleLoginClick() {
    this.setState({ status: 'ing' })
    request('/login', {
      params: {
        account: this.state.account,
        password: this.state.password,
      }
    }).then(() => {
      this.setState({status: 'succ'})
    }).catch(() => {
      this.setState({status: 'fail'})
    })
  }

  render() {
    return (
      <div>
        <input
          placeholder="帳號"
          value={this.state.account}
          onChange={(...args) => this.handleAccountChange(...args)}
        />
        <input
          placeholder="密碼"
          value={this.state.password}
          onChange={(...args) => this.handlePasswordChange(...args)}
        />
        <button onClick={() => this.handleLoginClick()}>登陸</button>
      </div>
    )
  }
}

拆分後

容器組件負責實現登陸功能,展現組件負責渲染內容。

若是要實現另外一套登錄組件時,可直接複用容器組件,只須要實現新的展現組件便可。

// 業務組件 可複用性比較高
function withLogin(config) {
  const { mapStateToProps, mapDispatchToProps } = config
  return (Comp) => {
    class Container extends Component {
      constructor(props) {
        super(props)

        this.state = {
          account: '',
          password: '',
          status: 'init',
        }
      }

      handleAccountChange = (e) => {
        this.setState({account: e.target.value})
      }

      handlePasswordChange = (e) => {
        this.setState({password: e.target.value})
      }

      handleLoginClick = () => {
        this.setState({ status: 'ing' })
        request('/login', {
          params: {
            account: this.state.account,
            password: this.state.password,
          }
        }).then(() => {
          this.setState({status: 'succ'})
        }).catch(() => {
          this.setState({status: 'fail'})
        })
      }

      render() {
        const propsFromState = mapStateToProps(this.state, this.props)
        const propsFromDispatch = mapDispatchToProps({
          onAccountChange: this.handleAccountChange,
          onPasswordChange: this.handlePasswordChange,
          onSubmit: this.handleLoginClick,
        }, this.props)
        return (
          <Comp
            {...this.props}
            {...propsFromState}
            {...propsFromDispatch}
          />
        )
      }
    }
    return LoginContainer
  }
}

// 展現組件
class Login extends Component {
  render() {
    const { account, password, onAccountChange, onPasswordChange, onSubmit }
    return (
      <div>
        <input
          placeholder="帳號"
          value={account}
          onChange={(...args) => onAccountChange(...args)}
        />
        <input
          placeholder="密碼"
          value={password}
          onChange={(...args) => onPasswordChange(...args)}
        />
        <button onClick={() => onSubmit()}>登陸</button>
      </div>
    )
  }
}

// 鏈接組件
const LoginContainer = withLogin({
  mapStateToProps: (state, props) => {
    return {
      account: state.account,
      password: state.password,
    }
  },
  mapDispatchToProps: (dispatch, props) => {
    return {
      onAccountChange: dispatch.onAccountChange,
      onPasswordChange: dispatch.onPasswordChange,
      onSubmit: dispatch.Submit,
    }
  }
})

渲染優化

把UI上相互獨立的部分,劃分紅不一樣組件,防止渲染時相互影響。最多見的是列表組件。

拆分前

點擊一個li, 其餘li全都從新渲染

class List extends Component {
  state = {
    selected: null
  }

  handleClick(id) {
    this.setState({selected: id})
  }

  render() {
    const { items } = this.props
    return (
      <ul>
      {
        items.map((item, index) => {
          const {text, id} = item
          const selected = this.state.selected === id
          return (
            <li
              key={id}
              className={selected ? 'selected' : ''}
              onClick={() => this.handleClick(id)}
            >
              <span>{text}</span>
            </li>
          )
        })
      }
      </ul>
    )
  }
}

拆分後

子組件使用PureComponentmemo,而且click事件回調函數直接使用this.handleClick,而不是每次都建立新函數。

點擊li,最多隻會有2個子組件渲染。

// onClick時須要的參數,要傳進來
class Item extends PureComponent {
  render() {
    const { id, text, selected, onClick } = this.props
    return (
      <li
        className={selected ? 'selected' : ''}
        onClick={onClick(id)}
      >
        <span>{text}</span>
      </li>
    )
  }
}

class List extends Component {
  state = {
    selected: null
  }

  handleClick(id) {
    this.setState({selected: id})
  }

  render() {
    const { items } = this.props
    return (
      <ul>
      {
        items.map((item, index) => {
          const {text, id} = item
          return (
            <Item
              key={id}
              id={id} // 傳進去
              selected={this.state.selected === id}
              text={text}
              onClick={this.handleClick}
            />
          )
        })
      }
      </ul>
    )
  }
}
相關文章
相關標籤/搜索