01、介紹
- React 高階元件也叫做 React HOC(High Order Component), 它是react中的高階技術, 用來重用元件邏輯。
- 但高階元件本身並不是React API。它只是一種模式,這種模式是由react自身的組合性質必然產生的。
- 那麼在學習高階元件之前有一個概念我們必須清楚,就是高階函式。
02、高階函式
- 概念:高階函式是一個函式,它接收函式作為引數或將函式作為輸出返回
- 舉個栗子:
- 接收函式作為引數
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 等方法
03、高階元件
- 概念:高階元件就是一個函式,且該函式接受一個元件作為引數,並返回一個新的元件
- 舉個栗子:
// WrappedComponent 就是傳入的包裝元件 function withHoc(WrappedComponent) { return class extends Component { render () { return ( <WrappedComponent /> ) } } } 複製程式碼
- withHoc 函式就是一個高階元件。那麼高階元件到底有什麼神奇的魔力,值得我們為之著迷?
- 開發元件時,我們會遇到相同的功能,使用高階元件則能減少重複程式碼
04、高階元件實訓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); 複製程式碼
- 現在我們提取了公共方法、狀態等資料, 封裝了一個基本的高階元件。 但是還有很多需要問題需要解決,現在開始行動~
05、高階元件實訓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);
06、高階元件實訓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> ) } } 複製程式碼
07、高階元件實訓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'; } 複製程式碼
- 修改之前名稱
- 修改之後名稱
08、使用裝飾器
- 目的: 簡化使用高階元件
- 下載包
- 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 ~~