一、组合
在 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'
// 创建一个 context
const ThemeContext = React.createContext('light')
class ThemeButton extends Component {
static contextType = ThemeContext
constructor (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)
【发上等愿,结中等缘,享下等福,择高处立,寻平处住,向宽处行】