React与Vue中的插槽
React和Vue都有插槽(slot)的概念,插槽(Slots)是一个组件化框架中的重要特性,它们用于组件的内容组合与布局。无论是在React还是在Vue中,插槽都扮演着类似的角色,即允许开发者将内容插入到组件的预留位置。其主要作用包括但不限于:
内容分发:插槽允许开发者定义组件的结构,在使用组件时可以动态插入内容。这让组件更加灵活,可以在不同的情况下重复使用。
自定义模板:通过插槽,父组件可以控制插入到子组件的内容的结构和样式。这个过程中,子组件不需要关心内容的具体实现,只需要提供插槽作为占位符。
代码复用:插槽可以减少重复代码。例如,你可能有一个模态框组件,它的头部、正文和尾部都可以使用插槽来自定义,这样你就可以重复使用同一个模态框组件,但每次都插入不同的内容。
作用域隔离:插槽可以帮助维持清晰的父子组件间的界限。子组件不需要知道父组件传递的具体内容,只关心插槽本身的位置和必要的数据接口。
提高性能:在某些情况下,使用插槽可以避免不必要的组件渲染,因为你可以控制何时和如何渲染插槽内容。
高级模式:Vue中的作用域插槽甚至可以让子组件传递数据回父组件,使得父组件可以基于子组件的数据来渲染内容。这为创建高度可定制的组件提供了强大的能力。
抽象和封装:插槽还可以用于高阶组件或封装库中,允许开发者在不暴露内部实现细节的情况下,提供给用户自定义的能力。
总的来说,插槽是组件间协作的强大工具,它们允许更高级的抽象,提高了代码的复用性和灵活性,并且在父子组件间建立了清晰的界面。
React的“插槽”机制:
在React中,并没有正式的“插槽”这个术语,React使用的是props.children
和渲染props(Render Props)模式来实现类似插槽的功能。
- props.children:
可以把props.children
看做是默认的插槽。当你在一个组件的开闭标签之间插入任何内容时,这些内容都会作为children
prop传递给组件。
1 2 3 4 5 6 7
| const MyComponent = (props) => { return <div>{props.children}</div>; };
<MyComponent> <p>This is some content</p> </MyComponent>
|
- 渲染Props(Render Props):
这是一种更为高级的模式,它允许用户通过一个函数将组件的状态或行为作为props传递给子组件。这在共享状态逻辑时非常有用。
1 2 3 4 5 6
| const MyComponent = ({ render }) => { const data = 'Data from MyComponent'; return <div>{render(data)}</div>; };
<MyComponent render={(data) => <div>{data}</div>} />
|
总结比较:
- React通过
props.children
和渲染props模式来实现内容分发,而Vue有专门的语法和概念来处理插槽。
- Vue的插槽更为直观,语法上更为明确,而React的模式更为灵活,但可能需要更多的JavaScript和React特定知识。
- Vue的作用域插槽使得父组件可以访问子组件数据并且可以在父组件的模板中使用这些数据,而React的类似功能通常是通过将函数作为子组件的props进行实现。
使用React插槽封装简单的Card组件
Card.jsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
import PropTypes from 'prop-types'; import React from 'react'; import './sass/card.sass'; const Card = ({ title, rigthBox, content, children }) => { let cardChiliren = React.Children.toArray(children); let RightBox = cardChiliren.filter(item => item.props.slot === 'RightBox'); let ContentBox = cardChiliren.filter(item => item.props.slot === 'ContentBox'); return ( <div className="card"> <div className="top_box"> <div className="box_left"> <div className="title">{title}</div> </div> <div className="box_right"> {RightBox.length > 0 ? RightBox : rigthBox} </div> </div> <div className="content">{ContentBox.length > 0 ? ContentBox : content}</div> </div> ); } Card.defaultProps = { title: 'title', rigthBox: 'rigthBox', content: 'content' }
Card.propTypes = { title: PropTypes.string.isRequired, content: PropTypes.string.isRequired, children: PropTypes.node }
export default Card;
|
使用Card组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
import React from 'react'; import ReactDOM from 'react-dom/client'; import Card from '@/components/card';
const root = ReactDOM.createRoot(document.getElementById('root')); const listStyle = { backgroundColor: 'yellowGreen', margin:'10px 0' } root.render( <> <Card title="card-title" content="card-content"> <div slot='ContentBox'> <div> {new Array(5).fill(null).map((item, index) => <div key={index} style={listStyle}>{`列表${index}`}</div>)} </div> </div> </Card> </> );
|
效果如下: