一、组合
在 React 中也有类似 Vue slot 的机制,既可以获取组件标签中包裹的内容,这种机制成为组合;
1.1 获取组件标签包裹的内容通过 props.children
- Panel.js
 
import React, { Component } from 'react'import 'bootstrap/dist/css/bootstrap.min.css'export default class Panel extends Component {render () {return (<div className="panel panel-danger">{this.props.children}</div>)}}
- 使用 Panel 组件,并且在其中嵌入内容
 
App.js
import React from 'react';import logo from './logo.svg';import './App.css';import ContextTest from './components/context';import Panel from './components/Panel'function App() {return (<div className="App"><ContextTest /><div className="container"><div className="col-md-6"><Panel><h1>这个是个头</h1><p>这是一段文字</p><footer>这个是个底部</footer></Panel></div></div></div>);}export default App;
2. 把指定内容插入到指定位置
- 改造 panel,要求头部、主体、尾部的内容通过父组件指定
 - Panel.js
 
import React, { Component } from 'react'import 'bootstrap/dist/css/bootstrap.min.css'export default class Panel extends Component {render () {return (<div className="panel panel-danger"><div className="panel-heading">{this.props.header}</div><div className="panel-body">{this.props.body}</div><div className="panel-footer">{this.props.footer}</div></div>)}}
- 使用 Panel,并且指定头、主体、尾部
 - App.js
 
import React from 'react';import logo from './logo.svg';import './App.css';import ContextTest from './components/context';import Panel from './components/Panel'function App() {let h1 = <h1>这个是个头</h1>let body = <div><p>这是主题</p></div>let footer = <footer><button className="btn">取消</button></footer>return (<div className="App"><ContextTest /><div className="container"><div className="col-md-6"><Panel><p>这是一段文字</p></Panel></div><div className="container"><div className="col-md-6 col-md-offset-3"><Panel header={h1} body={body} footer={footer}></Panel></div></div></div></div>);}export default App;
二、Context
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
2.1 Context 能干什么?
举一个例子,现在有 ThemeButton 组件、Toolbar 组件、App 组件;App 组件需要指定一个主题色,App 中有一个 Toolbar,Toolbar 中有一个 ThemeButton,ThemeButton 则会根据主题色显示不同的颜色;所以这个主题色 theme prop 将会从 App 传递给 Toolbar ,然后再由 Toolbar 传递给 ThemeButton
- 示例代码:
 
class ThemeButton extends Component {constructor (props, context) {super()console.log(context)}render() {return (<div><button className={`btn btn-xs btn-${this.props.theme}`}>确定</button></div>);}}class Toolbar extends Component {render () {return <div><ThemeButton theme={this.props.theme} /></div>}}export default class App extends Component {render () {return (<div><Toolbar theme="danger" /></div>)}}
- 这样如果层级再深一些,传递起来很繁琐,为了解决这个问题我们需要使用 Context ;
 
2.2 Context 用法
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。使用 Context 后,我们上面的例子中 ThemeButton 获取主题色时,就不需要让 Toolbar 传递了;
1. 创建一个 Context
使用 React.createContext(defaultValue) defaultValue 是一个默认值;
const ThemeContext = React.createContext('success')
2. Context.Provider
我们通过 Context.Provider 组件向组件树中引入要共享的值,值通过 value 属性指定;示例:
<ThemeContext.Provider value="danger"></ThemeContext.Provider>
- App 组件需要和 ThemeButton 共享主题色,而且 App 为上层组件,所以它需要提供这个主题色
 
export default class App extends Component {render () {return (<div><ThemeContext.Provider value="dark"><Toolbar /></ThemeContext.Provider></div>)}
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 函数中也可以;
 - 示例
 
import React, { Component } from 'react'// 创建一个 contextconst ThemeContext = React.createContext('light')class ThemeButton extends Component {static contextType = ThemeContextconstructor (props, context) {super()console.log(context)}render() {return (<div><button className={`btn btn-${this.context.theme}`}>{this.context.theme}</button></div>)}}class Toolbar extends Component {render () {return <div><ThemeButton /></div>}}export default class App extends Component {render () {return (<div><ThemeContext.Provider value={{theme: 'success'}}><Toolbar /></ThemeContext.Provider></div>)}}
3. Context.Consumer
Consumer,在函数式的组件中使用 context;在函数式的组件中使用 Context 共享的值需要使用 Consumer 组件
示例:
function ThemeButton(props) {return (<ThemeContext.Consumer>{value => <button className={`btn btn-${value.theme}`}>{value.theme}</button>}</ThemeContext.Consumer>)}
三、高阶组件
高阶组件是参数为组件,返回值为新组件的函数。
本地有两个组件,Username 和 Password;Username 的作用是从 localStorage 中读取 username 属性,并且展示到 input 中;Password 组件作用类似,不同的是它展示的是从 localStorage 中读取的 password 字段;
3.1 Username 和 Password;
- Username.js
 
class Username extends Component {constructor () {super()this.state = {username: ''}}componentWillMount () {let username = localStorage.getItem('username')this.setState({username: username})}render () {return <input type="text" defaultValue={this.state.username}/>}}
- Password.js
 
import React, { Component } from 'react'export default class Password extends Component {constructor () {super()this.state = {password: ''}}componentWillMount () {let password = localStorage.getItem('password')this.setState({password: password})}render () {return <input type="text" defaultValue={this.state.password}/>}}
我们发现组件中很多代码功能都是相同的,此时我们可以把在抽象成一个高阶组件,把不同的部分当成参数:如上面的从 localStorage 中获取的字段名不同,最终需要的组件不同;
3.2 Local.js 高阶组件
import React, { Component } from 'react'let local = (key) => (Component) => {return class HighOrderComponent extends React.Component {constructor () {super()this.state = {[key]: ''}}componentWillMount () {let val = localStorage.getItem(key)this.setState({[key]: val})}render () {return <Component {...this.state} />}}}export default local
3.4 改造 Username 和 Password
- Username.js
 
import React, { Component } from 'react'import local from './Local'class Username extends Component {render () {return <input type="text" defaultValue={this.props.username}/>}}export default local('username')(Username)
- Password.js
 
import React, { Component } from 'react'import local from './Local'class Password extends Component {render () {return <input type="text" defaultValue={this.props.password}/>}}export default local('password')(Password)
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】
