珠峰培训,专注前端

一、组合

在 React 中也有类似 Vue slot 的机制,既可以获取组件标签中包裹的内容,这种机制成为组合;

1.1 获取组件标签包裹的内容通过 props.children

  • Panel.js
  1. import React, { Component } from 'react'
  2. import 'bootstrap/dist/css/bootstrap.min.css'
  3. export default class Panel extends Component {
  4. render () {
  5. return (<div className="panel panel-danger">
  6. {this.props.children}
  7. </div>)
  8. }
  9. }
  • 使用 Panel 组件,并且在其中嵌入内容

App.js

  1. import React from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4. import ContextTest from './components/context';
  5. import Panel from './components/Panel'
  6. function App() {
  7. return (
  8. <div className="App">
  9. <ContextTest />
  10. <div className="container">
  11. <div className="col-md-6">
  12. <Panel>
  13. <h1>这个是个头</h1>
  14. <p>这是一段文字</p>
  15. <footer>这个是个底部</footer>
  16. </Panel>
  17. </div>
  18. </div>
  19. </div>
  20. );
  21. }
  22. export default App;

2. 把指定内容插入到指定位置

  • 改造 panel,要求头部、主体、尾部的内容通过父组件指定
  • Panel.js
  1. import React, { Component } from 'react'
  2. import 'bootstrap/dist/css/bootstrap.min.css'
  3. export default class Panel extends Component {
  4. render () {
  5. return (<div className="panel panel-danger">
  6. <div className="panel-heading">
  7. {this.props.header}
  8. </div>
  9. <div className="panel-body">
  10. {this.props.body}
  11. </div>
  12. <div className="panel-footer">
  13. {this.props.footer}
  14. </div>
  15. </div>)
  16. }
  17. }
  • 使用 Panel,并且指定头、主体、尾部
  • App.js
  1. import React from 'react';
  2. import logo from './logo.svg';
  3. import './App.css';
  4. import ContextTest from './components/context';
  5. import Panel from './components/Panel'
  6. function App() {
  7. let h1 = <h1>这个是个头</h1>
  8. let body = <div><p>这是主题</p></div>
  9. let footer = <footer><button className="btn">取消</button></footer>
  10. return (
  11. <div className="App">
  12. <ContextTest />
  13. <div className="container">
  14. <div className="col-md-6">
  15. <Panel>
  16. <p>这是一段文字</p>
  17. </Panel>
  18. </div>
  19. <div className="container">
  20. <div className="col-md-6 col-md-offset-3">
  21. <Panel header={h1} body={body} footer={footer}></Panel>
  22. </div>
  23. </div>
  24. </div>
  25. </div>
  26. );
  27. }
  28. export default App;

二、Context

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

2.1 Context 能干什么?

举一个例子,现在有 ThemeButton 组件、Toolbar 组件、App 组件;App 组件需要指定一个主题色,App 中有一个 Toolbar,Toolbar 中有一个 ThemeButton,ThemeButton 则会根据主题色显示不同的颜色;所以这个主题色 theme prop 将会从 App 传递给 Toolbar ,然后再由 Toolbar 传递给 ThemeButton

  • 示例代码:
  1. class ThemeButton extends Component {
  2. constructor (props, context) {
  3. super()
  4. console.log(context)
  5. }
  6. render() {
  7. return (
  8. <div>
  9. <button className={`btn btn-xs btn-${this.props.theme}`}>确定</button>
  10. </div>
  11. );
  12. }
  13. }
  14. class Toolbar extends Component {
  15. render () {
  16. return <div>
  17. <ThemeButton theme={this.props.theme} />
  18. </div>
  19. }
  20. }
  21. export default class App extends Component {
  22. render () {
  23. return (<div>
  24. <Toolbar theme="danger" />
  25. </div>)
  26. }
  27. }
  • 这样如果层级再深一些,传递起来很繁琐,为了解决这个问题我们需要使用 Context ;

2.2 Context 用法

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。使用 Context 后,我们上面的例子中 ThemeButton 获取主题色时,就不需要让 Toolbar 传递了;

1. 创建一个 Context

使用 React.createContext(defaultValue) defaultValue 是一个默认值;

  1. const ThemeContext = React.createContext('success')

2. Context.Provider

我们通过 Context.Provider 组件向组件树中引入要共享的值,值通过 value 属性指定;示例:

  1. <ThemeContext.Provider value="danger"></ThemeContext.Provider>
  • App 组件需要和 ThemeButton 共享主题色,而且 App 为上层组件,所以它需要提供这个主题色
  1. export default class App extends Component {
  2. render () {
  3. return (<div>
  4. <ThemeContext.Provider value="dark">
  5. <Toolbar />
  6. </ThemeContext.Provider>
  7. </div>)
  8. }

3. Context.contextType

  • 如果是 class 声明的组件,底层组件使用上面 Provider 提供的值需要在组件中声明 contextType 静态属性,值是要获取值的 Context;然后在组件中就可以通过 this.context 获取 Provider 的 value 属性指定的共享的值了。如果在 constructor 中使用 context ,constructor 的第二个参数就是 context 的值;
  • React 会向上层找,找到最近的 ThemeContext 的 Provider 的 value ,这个 value 会传给 constructor 的第二个参数;在 constructor 以外的其他函数中可以通过 this.context 访问该值,render 函数中也可以;
  • 示例
  1. import React, { Component } from 'react'
  2. // 创建一个 context
  3. const ThemeContext = React.createContext('light')
  4. class ThemeButton extends Component {
  5. static contextType = ThemeContext
  6. constructor (props, context) {
  7. super()
  8. console.log(context)
  9. }
  10. render() {
  11. return (
  12. <div>
  13. <button className={`btn btn-${this.context.theme}`}>{this.context.theme}</button>
  14. </div>
  15. )
  16. }
  17. }
  18. class Toolbar extends Component {
  19. render () {
  20. return <div>
  21. <ThemeButton />
  22. </div>
  23. }
  24. }
  25. export default class App extends Component {
  26. render () {
  27. return (<div>
  28. <ThemeContext.Provider value={{theme: 'success'}}>
  29. <Toolbar />
  30. </ThemeContext.Provider>
  31. </div>)
  32. }
  33. }

3. Context.Consumer

Consumer,在函数式的组件中使用 context;在函数式的组件中使用 Context 共享的值需要使用 Consumer 组件

示例:

  1. function ThemeButton(props) {
  2. return (<ThemeContext.Consumer>
  3. {
  4. value => <button className={`btn btn-${value.theme}`}>{value.theme}</button>
  5. }
  6. </ThemeContext.Consumer>)
  7. }

三、高阶组件

高阶组件是参数为组件,返回值为新组件的函数。

本地有两个组件,Username 和 Password;Username 的作用是从 localStorage 中读取 username 属性,并且展示到 input 中;Password 组件作用类似,不同的是它展示的是从 localStorage 中读取的 password 字段;

3.1 Username 和 Password;

  • Username.js
  1. class Username extends Component {
  2. constructor () {
  3. super()
  4. this.state = {
  5. username: ''
  6. }
  7. }
  8. componentWillMount () {
  9. let username = localStorage.getItem('username')
  10. this.setState({
  11. username: username
  12. })
  13. }
  14. render () {
  15. return <input type="text" defaultValue={this.state.username}/>
  16. }
  17. }
  • Password.js
  1. import React, { Component } from 'react'
  2. export default class Password extends Component {
  3. constructor () {
  4. super()
  5. this.state = {
  6. password: ''
  7. }
  8. }
  9. componentWillMount () {
  10. let password = localStorage.getItem('password')
  11. this.setState({
  12. password: password
  13. })
  14. }
  15. render () {
  16. return <input type="text" defaultValue={this.state.password}/>
  17. }
  18. }

我们发现组件中很多代码功能都是相同的,此时我们可以把在抽象成一个高阶组件,把不同的部分当成参数:如上面的从 localStorage 中获取的字段名不同,最终需要的组件不同;

3.2 Local.js 高阶组件

  1. import React, { Component } from 'react'
  2. let local = (key) => (Component) => {
  3. return class HighOrderComponent extends React.Component {
  4. constructor () {
  5. super()
  6. this.state = {
  7. [key]: ''
  8. }
  9. }
  10. componentWillMount () {
  11. let val = localStorage.getItem(key)
  12. this.setState({
  13. [key]: val
  14. })
  15. }
  16. render () {
  17. return <Component {...this.state} />
  18. }
  19. }
  20. }
  21. export default local

3.4 改造 Username 和 Password

  • Username.js
  1. import React, { Component } from 'react'
  2. import local from './Local'
  3. class Username extends Component {
  4. render () {
  5. return <input type="text" defaultValue={this.props.username}/>
  6. }
  7. }
  8. export default local('username')(Username)
  • Password.js
  1. import React, { Component } from 'react'
  2. import local from './Local'
  3. class Password extends Component {
  4. render () {
  5. return <input type="text" defaultValue={this.props.password}/>
  6. }
  7. }
  8. export default local('password')(Password)

【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】