從零開始學習 React 高階組件

0一、介紹

  • React 高階組件也叫作 React HOC(High Order Component), 它是react中的高級技術, 用來重用組件邏輯。
  • 但高階組件自己並非React API。它只是一種模式,這種模式是由react自身的組合性質必然產生的。
  • 那麼在學習高階組件以前有一個概念咱們必須清楚,就是高階函數。

0二、高階函數

  • 概念:高階函數是一個函數,它接收函數做爲參數或將函數做爲輸出返回
  • 舉個栗子:
    • 接收函數做爲參數
      function a(x) {
        x();
      }
      function b() {
        alert('hello');
      }
      
      a(b);
      複製代碼
    • 將函數做爲輸出返回
      function a() {
        function b() {
          alert('hello');
        }
        return b;
      }
      
      a()();
      複製代碼
  • 以上函數a就是一個高階函數, 用法很是簡單, 那麼實際開發中又有哪些是高階函數呢?
    • Array 的 map 、reduce 、filter 等方法
    • Object 的 keys 、values 等方法

0三、高階組件

  • 概念:高階組件就是一個函數,且該函數接受一個組件做爲參數,並返回一個新的組件
  • 舉個栗子:
    // WrappedComponent 就是傳入的包裝組件
    function withHoc(WrappedComponent) {
      return class extends Component {
        render () {
          return (
            <WrappedComponent />
          )
        }
      }
    }
    複製代碼
  • withHoc 函數就是一個高階組件。那麼高階組件到底有什麼神奇的魔力,值得咱們爲之着迷?
  • 開發組件時,咱們會遇到相同的功能,使用高階組件則能減小重複代碼

0四、高階組件實訓1

  • 目的: 定義高階組件
  • 組件 Login -- 登錄頁面
    // 受控組件
    class Login extends Component {
      state = {
        username: '',
        password: ''
      }
      
      onUsernameChange = (e) => {
        this.setState({username: e.target.value});
      }
      
      onPasswordChange = (e) => {
        this.setState({password: e.target.value});
      }
      
      login = (e) => {
        // 禁止默認事件
        e.preventDefault();
        // 收集表單數據
        const { username, password } = this.state;
        alert(`用戶名: ${username}, 密碼: ${password}`);
      }
      
      render () {
        const { username, password } = this.state;
        return (
          <div>
            <h2>登錄</h2>
            <form onSubmit={this.login}>
              用戶名: <input type="text" name="username" value={username} onChange={this.onUsernameChange}/> <br/>
              密碼: <input type="password" name="password" value={password} onChange={this.onPasswordChange}/> <br/>
              <input type="submit" value="登錄"/>
            </form>
          </div>
        )
      }
    }
    複製代碼
  • 組件 Register -- 註冊頁面
    // 受控組件
    class Register extends Component {
      state = {
        username: '',
        password: '',
        rePassword: ''
      }
      
      onUsernameChange = (e) => {
        this.setState({username: e.target.value});
      }
      
      onPasswordChange = (e) => {
        this.setState({password: e.target.value});
      }
      
      onRePasswordChange = (e) => {
        this.setState({rePassword: e.target.value});
      }
      
      register = (e) => {
        // 禁止默認事件
        e.preventDefault();
        // 收集表單數據
        const { username, password, rePassword } = this.state;
        alert(`用戶名: ${username}, 密碼: ${password}, 確認密碼: ${rePassword}`);
      }
      
      render () {
        const { username, password, rePassword } = this.state;
        return (
          <div>
            <h2>註冊</h2>
            <form onSubmit={this.register}>
              用戶名: <input type="text" name="username" value={username} onChange={this.onUsernameChange}/> <br/>
              密碼: <input type="password" name="password" value={password} onChange={this.onPasswordChange}/> <br/>
              確認密碼: <input type="password" name="rePassword" value={rePassword} onChange={this.onRePasswordChange}/> <br/>
              <input type="submit" value="註冊"/>
            </form>
          </div>
        )
      }
    }
    複製代碼
  • 頁面效果
  • 咱們發現裏面重複邏輯實在太多了,尤爲是 onXxxChange 函數出現太多,咱們先優化一下。
    // 咱們以 Register 組件爲例來看
    class Register extends Component {
      state = {
        username: '',
        password: '',
        rePassword: ''
      }
      // 最終修改狀態數據的函數
      onChange = (stateName, stateValue) => {
        this.setState({[stateName]: stateValue});
      }
      // 高階函數 --> 這樣後面就能一直複用當前函數,而不用從新建立了~
      composeChange = (name) => {
        return (e) => this.onChange(name, e.target.value);
      }
      // 統一全部提交表單函數名
      handleSubmit = (e) => {
        e.preventDefault();
        const { username, password, rePassword } = this.state;
        alert(`用戶名: ${username}, 密碼: ${password}, 確認密碼: ${rePassword}`);
      }
      
      render () {
        const { username, password, rePassword } = this.state;
        return (
          <div>
            <h2>註冊</h2>
            <form onSubmit={this.handleSubmit}>
              用戶名: <input type="text" name="username" value={username} onChange={this.composeChange('username')}/> <br/>
              密碼: <input type="password" name="password" value={password} onChange={this.composeChange('password')}/> <br/>
              確認密碼: <input type="password" name="rePassword" value={rePassword} onChange={this.composeChange('rePassword')}/> <br/>
              <input type="submit" value="註冊"/>
            </form>
          </div>
        )
      }
    }
    複製代碼
  • 如今兩個頁面都有 onChange 、 composeChange 、handleSubmit 函數和相關的狀態,咱們接下來提取,封裝成高階組件
    // 高階組件 withHoc
    export default function withHoc(WrappedComponent) {
      return class extends Component {
        state = {
          username: '',
          password: '',
          rePassword: ''
        }
      
        onChange = (stateName, stateValue) => {
          this.setState({[stateName]: stateValue});
        }
      
        composeChange = (name) => {
          return (e) => this.onChange(name, e.target.value);
        }
        
        handleSubmit = (e) => {
          e.preventDefault();
          const { username, password, rePassword } = this.state;
          if (rePassword) {
            alert(`用戶名: ${username}, 密碼: ${password}, 確認密碼: ${rePassword}`);
          } else {
            alert(`用戶名: ${username}, 密碼: ${password}`);
          }
        }
        
        render () {
          // 抽取方法
          const mapMethodToProps = {
            composeChange: this.composeChange,
            handleSubmit: this.handleSubmit,
          }
          // 將狀態數據和操做的方法以 props 的方式傳入的包裝組件中
          return (
            <div>
              {/*提取公共頭部*/}
              <h2>xxx</h2>
              <WrappedComponent {...this.state} {...mapMethodToProps}/>
            </div>
          )
        }
      }
    }
    
    // 組件 Register
    class Register extends Component {
      render () {
        const { handleSubmit, composeChange, username, password, rePassword } = this.props;
        return (
          <form onSubmit={handleSubmit}>
            用戶名: <input type="text" name="username" value={username} onChange={composeChange('username')}/> <br/>
            密碼: <input type="password" name="password" value={password} onChange={composeChange('password')}/> <br/>
            確認密碼: <input type="password" name="rePassword" value={rePassword} onChange={composeChange('rePassword')}/> <br/>
            <input type="submit" value="註冊"/>
          </form>
        )
      }
    }
    // 向外暴露的是高階組件的返回值~包裝了 Register 組件返回了一個新組件
    export default withHoc(Register);
    複製代碼
  • 如今咱們提取了公共方法、狀態等數據, 封裝了一個基本的高階組件。 可是還有不少須要問題須要解決,如今開始行動~

0五、高階組件實訓2

  • 目的: 向高階組件中傳參
  • 修改高階組件
    // 再次包裹了一層高階函數, 這個高階函數執行後返回值纔是高階組件
    // 經過這種方式, 高階組件內部就能獲取參數了~
    export default (title) => (WrappedComponent) => {
      return class Form extends Component {
        ...重複代碼省略...
        
        render () {
          const mapMethodToProps = {
            composeChange: this.composeChange,
            handleSubmit: this.handleSubmit,
          }
          return (
            <div>
              {/*獲取到參數值就能正常顯示了~*/}
              <h2>{title}</h2>
              <WrappedComponent {...this.state} {...mapMethodToProps}/>
            </div>
          )
        }
      }
    }
    複製代碼
  • 在 Login / Register 組件中使用
    • export default withHoc('登錄')(Login);
    • export default withHoc('註冊')(Register);

0六、高階組件實訓3

  • 目的: 獲取父組件傳遞的 props
  • 修改 App 組件
    class App extends Component {
      render() {
        return (
          <div>
            {/*父組件向子組件傳遞屬性*/}
            <Login name="jack" age={18}/>
            <Register />
          </div>
        );
      }
    }
    複製代碼
  • 修改高階組件
    export default (title) => (WrappedComponent) => {
      return class Form extends Component {
        ...重複代碼省略...
        
        render () {
          const mapMethodToProps = {
            composeChange: this.composeChange,
            handleSubmit: this.handleSubmit,
          }
          return (
            <div>
              {/*獲取到參數值就能正常顯示了~*/}
              <h2>{title}</h2>
              {/* 將當前組件接受到的props傳給包裝組件~*/}
              <WrappedComponent {...this.props} {...this.state} {...mapMethodToProps}/>
            </div>
          )
        }
      }
    }
    複製代碼
  • Login 組件中使用
    class Login extends Component {
       render () {
         const { handleSubmit, composeChange, username, password, name, age } = this.props;
         return (
           <div>
             <p>你的名字: {name}</p>
             <p>你的年齡: {age}</p>
             <form onSubmit={handleSubmit}>
               用戶名: <input type="text" name="username" value={username} onChange={composeChange('username')}/> <br/>
               密碼: <input type="password" name="password" value={password} onChange={composeChange('password')}/> <br/>
               <input type="submit" value="登錄"/>
             </form>
           </div>
         )
       }
     }
    複製代碼

0七、高階組件實訓4

  • 目的: 修改在 React-devtool 中高階組件名稱,方便調試
  • 修改高階組件
    export default (title) => (WrappedComponent) => {
      return class Form extends Component {
        // 定義靜態方法,修改組件在調試工具中顯示的名稱
        static displayName = `Form(${getDisplayName(WrappedComponent)})`
        
        ...省略重複代碼...
      }
    }
    // 獲取包裝組件的displayName的方法
    function getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }
    複製代碼
  • 修改以前名稱
  • 修改以後名稱

0八、使用裝飾器

  • 目的: 簡化使用高階組件
  • 下載包
    • npm i react-app-rewired customize-cra @babel/plugin-proposal-decorators -D
  • 在項目根目錄配置 config-overrides.js
    const { override, addDecoratorsLegacy } = require('customize-cra');
    // 修改 create-react-app 的 webpack 的配置
    module.exports = override(
      addDecoratorsLegacy()
    )
    複製代碼
  • 修改 package.json 的 scripts
    // 將 react-scripts 修改成 react-app-rewired
    "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test"
    },
    複製代碼
  • 以上就是使用 decorator 的配置,修改完後就能使用了~
  • 修改 Login 組件
    @withHoc('登錄')
    class Login extends Component {
      ...省略重複代碼...
    }
    export default Login;
    複製代碼
  • 修改 Register 組件
    @withHoc('註冊')
    class Register extends Component {
      ...省略重複代碼...
    }
    export default Register;
    複製代碼
  • react-app-rewired customize-cra 是 create-react-app 2.0以上專門用來修改 webpack 的配置
  • decorator 還能作不少事,感興趣朋友能夠看看 阮一峯ES6教程 瞭解更多

重複代碼永遠是咱們須要考慮處理的代碼,因此咱們有模塊化、組件化、工具類函數等等, 在 React 中再次引入了一個高階組件的概念,都是爲了去除掉萬惡的重複代碼,讓咱們代碼變得更加精簡。 本篇文章全部源碼都放在了 git倉庫,若是它對你有幫助的話,歡迎點 star ~~react

相關文章
相關標籤/搜索