共计 3240 个字符,预计需要花费 9 分钟才能阅读完成。
首先来看下 vue 的 slot 的实现
<base-layout> 组件,具名插槽 name 定义以及默认插槽
<div class="container"> | |
<header> | |
<slot name="header"></slot> | |
</header> | |
<main> | |
<slot></slot> | |
</main> | |
<footer> | |
<slot name="footer"></slot> | |
</footer> | |
</div> |
<template> 提供内容渲染的组件
<base-layout> | |
<template v-slot:header> | |
<h1>Here might be a page title</h1> | |
</template> | |
<p>A paragraph for the main content.</p> | |
<p>And another one.</p> | |
<template v-slot:footer> | |
<p>Here's some contact info</p> | |
</template> | |
</base-layout> |
最终会渲染出已下架结构
<div class="container"> | |
<header> | |
<h1>Here might be a page title</h1> | |
</header> | |
<main> | |
<p>A paragraph for the main content.</p> | |
<p>And another one.</p> | |
</main> | |
<footer> | |
<p>Here's some contact info</p> | |
</footer> | |
</div> |
言归正传,怎样使用 react 的 context 实现 vue 的这一功能呢
1 首先确认下 layout 组件的结构
import React, {Component} from 'react'; | |
import SlotProvider from './SlotProvider' | |
import Slot from './Slot' | |
class AppLayout extends Component { | |
static displayName = 'AppLayout' | |
render () { | |
return ( | |
<div className="container"> | |
<header> | |
<Slot name="header"></Slot> | |
</header> | |
<main> | |
<Slot></Slot> | |
</main> | |
<footer> | |
<Slot name="footer"></Slot> | |
</footer> | |
</div> | |
) | |
} | |
} | |
export default SlotProvider(AppLayout) |
2 对此结构输出具体内容的组件
import React, {Component} from 'react'; | |
import AppLayout from './AppLayout' | |
import AddOn from './AddOn' | |
export default class App extends Component {render() { | |
return ( | |
<AppLayout> | |
<AddOn slot="header"> | |
<h1> 这里可能是一个页面标题 </h1> | |
</AddOn> | |
<AddOn> | |
<p> 主要内容的一个段落。</p> | |
<p> 另一个段落。</p> | |
</AddOn> | |
<AddOn slot="footer"> | |
<p> 这里有一些联系信息 </p> | |
</AddOn> | |
</AppLayout> | |
) | |
} | |
} |
3 其中 AddOn 类似于上面 vue 的 template,所以下面来实现这个简单的只是用来提供 slot 标识和 children 内容的组件 AddOn 的实现
import React from 'react'; | |
import PropTypes from 'prop-types' | |
const AddOn = () => null | |
AddOn.propTypes = {slot: PropTypes.string} | |
AddOn.defaultTypes = {slot: '$$default'} | |
AddOn.displayName = 'AddOn' | |
export default AddOn |
4 Slot 组件
import React from 'react'; | |
import {SlotContext} from './SlotProvider' | |
import PropTypes from 'prop-types' | |
const Slot = ({name, children}) => { | |
return ( | |
<SlotContext.Consumer> | |
{(value) => {const addOnRenderer = value.requestAddOnRenderer(name) | |
return (addOnRenderer && addOnRenderer()) || children || null | |
}} | |
</SlotContext.Consumer> | |
) | |
} | |
Slot.displayName = 'Slot' | |
Slot.propTypes = {name: PropTypes.string} | |
Slot.defaultProps = {name: '$$default'} | |
export default Slot |
5 接下来就是对 Slot 进行解析的 HOC SlotProvider 组件了
import React, {Component} from 'react'; | |
function getDisplayName(component) {return component.displayName || component.name || 'component'} | |
export const SlotContext = React.createContext({requestAddOnRenderer: () => {}}) | |
const SlotProviderHoC = (WrappedComponent) => { | |
return class extends Component {static displayName = `SlotProvider(${getDisplayName(WrappedComponent)})` | |
// 用于缓存每个 <AddOn /> 的内容 | |
addOnRenderers = {} | |
requestAddOnRenderer = (name) => {if (!this.addOnRenderers[name]) {return undefined} | |
return () => (this.addOnRenderers[name] | |
) | |
} | |
render() { | |
const { | |
children, | |
...restProps | |
} = this.props | |
if (children) { | |
// 以 k - v 的方式缓存 <AddOn /> 的内容 | |
const arr = React.Children.toArray(children) | |
const nameChecked = [] | |
this.addOnRenderers = {} | |
arr.forEach(item => { | |
const itemType = item.type | |
console.log('itemType',itemType) | |
if (item.type.displayName === 'AddOn') { | |
const slotName = item.props.slot || '$$default' | |
// 确保内容唯一性 | |
if (nameChecked.findIndex(item => item === slotName) !== -1) {throw new Error(`Slot(${slotName}) has been occupied`) | |
} | |
this.addOnRenderers[slotName] = item.props.children | |
nameChecked.push(slotName) | |
} | |
}) | |
} | |
return (<SlotContext.Provider value={{ requestAddOnRenderer: this.requestAddOnRenderer}}> | |
<WrappedComponent {...restProps} /> | |
</SlotContext.Provider > | |
) | |
} | |
} | |
} | |
export default SlotProviderHoC |
6 最终渲染结果
摘抄:张国钰
正文完
发表至: javascript
2019-07-18