18 组合(类似 vue 组件的插槽)
在 Vue 中, 假如我们需要让子组件的一部分内容, 被父组件控制, 而不是被子组件控制, 那么我们会采用插槽的写法 <slot></slot>
在 React 里也有类似的写法, 父组件写法是相同的, 但子组件是采用 {this.props.children} 来实现
示例:
- class MyChild extends React.Component { render() {
- return < p > {
- this.props.children
- } < /p>
- }
- }
- class WelcomeDialog extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- date: (new Date()).toLocaleTimeString()
- }
- setInterval(() => {
- / / 显示内容全部由父组件控制,
- 子组件不关心父组件显示什么,
- 只关心显示在哪里 this.setState({ date: (new Date()).toLocaleTimeString()
- })
- },
- 1000)
- } render() {
- return < p > < MyChild > {
- this.state.date
- } < /MyChild>
- </p >
- }
- }
如上代码, 子组件展现的内容, 是父组件中, 子组件标签内含的内容
关于 this.props.children 这个变量:
以 < MyChild > 为例, 注意, 这个名字只跟子组件名挂钩, 不是固定的
字符串:<MyChild > 标签内是一个普通的字符串;
对 象:<MyChild > 标签内是一个 DOM 元素, 例如:<MyChild> <span>{this.state.date}</span> </MyChild>;
数 组:<MyChild > 标签内是多个 DOM 元素, 例如:<MyChild> <span>{this.state.date}</span> <span>{this.state.date}</span> </MyChild>;
如何获取 <MyChild> 标签内的二级或者更多级元素?
以以下为例:
- <MyChild>
- <p>
- abc
- <span>{this.state.date}</span>
- </p>
- </MyChild>
显然, 标签内必须是一个 DOM 标签;
DOM 标签里有二级元素, 以上为例, 有两个元素, 分别是一个字符串 abc 和一个 span 标签;
那么如何获取这两个元素呢? 通过 this.props.children.props.children 来获取(第一个 children 指传递进来的元素, 第二个指二级元素);
this.props.children.props.children 在以上情况下, 是一个数组, 数组元素一是字符串 abc, 数组元素二, 是对象(即 span 标签);
props 这个属性, 不是固定类型的, 可能是字符串, 也可能是数组或对象, 根据该级元素是什么, 以及有几个而决定;
示例代码:
- class MyChild extends React.Component {
- constructor(props) {
- super(props)
- console.log(this.props)
- }
- render() {
- return <p>
父元素的第一个字符串:{this.props.children.props.children[0]}
<br/>
父元素的第二个字符串:{this.props.children.props.children[1]}
- </p>
- }
- }
- class WelcomeDialog extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- date: (new Date()).toLocaleTimeString(),
- year: (new Date()).toLocaleDateString()
- }
- setInterval(() => {
- // 显示内容全部由父组件控制, 子组件不关心父组件显示什么, 只关心显示在哪里
- this.setState({
- date: (new Date()).toLocaleTimeString(),
- year: (new Date()).toLocaleDateString()
- })
- }, 1000)
- }
- render() {
- return <p>
- <MyChild>
- <p>
- {this.state.date}
- {this.state.year}
- </p>
- </MyChild>
- </p>
- }
- }
当然, 如果是纯字符串的话, 通过变量 props 直接传入更好一些
而以上这种写法, 更适合传 JSX 语法的 DOM 元素
19 继承(事实上还是组合, 但展现的效果像是继承)
我们有时候会面临这样一个情况:
有一个表单;
表单里有单行输入框 (input) 多行输入框(textarea), 也许还有其他的比如单选框, 或多选框;
输入框的样式是统一的, 所以希望统一管理;
但每个输入框的验证逻辑不同;
每个输入框都有自己的错误提示;
在往常情况下, 我们是这么解决的:
将每个输入框单独拆分成组件, 然后表单里依次引入这些组件;
对于 CSS, 因为样式相同, 所以采用的是统一的 class, 因此可能需要将这些样式单独写入某个 css 文件, 然后在这些样式组件里引入并使用这些类;
在每个组件里写样式, 逻辑等;
如果多个样式他们有相同的逻辑, 可能需要复制粘贴(优化方式是将这些验证逻辑单独抽象到一个 js 文件, 然后在这些组件里引用这个 js 文件, 并使用这些逻辑);
示例略
这种写法, 讲道理说, 对于一般项目来说, 也足够了, 优化程度也不差;
但毕竟还有更好的写法, 先提思路:
组件分为基础组件和扩展组件;
基础组件是输入框, 有样式 html 元素基本的验证逻辑一些通用的逻辑等(比如通用错误提示);
扩展组件里内置基础组件, 负责 控制值 (传到基础组件, 但是在这一层控制) 验证逻辑 (显然姓名电话日期输入框, 他们的验证逻辑是不同的) 提示信息 (比如 HTML 标签的 title 属性) 等;
表单里引入扩展组件;
这意味着, 我们在使用的时候, 只需要从扩展组件里引入基础组件即可;
在写扩展组件时, 只需要专心于逻辑, 不需要关心样式等通用性问题;
而写基础组件的时候, 只需要关心共性, 而不需要关心逻辑, 有逻辑则使用逻辑, 没有逻辑则执行空函数即可;
附代码(结尾见解释):
- // 基础组件
- class BaseInput extends React.Component {
- render() {
- let left = {display: 'inline-block', width: '100px'}
- let right = {display: 'inline-block', width: '200px', boxSizing: 'border-box'}
- let DOM
- let changeFn
- if (this.props.onChange) {
- changeFn = e => {
- this.props.onChange(e)
- }
- } else {
- changeFn = () => {
- }
- }
- // 首先, 根据类型选择需要的输入框
- if (this.props.type === 'input' | !this.props.type) {
- DOM = <span>
- <span style={left}>{this.props.label}</span>
- <input style={right} type="text"
- onChange={changeFn}
- value={this.props.value}/>
- </span>
- } else if (this.props.type === 'textarea') {
- DOM = <span>
- <span style={left}>{this.props.label}</span>
- <textarea style={right} type="text" onChange={changeFn}
- value={this.props.value}/>
- </span>
- }
- return <p style={{height: '50px'}}>
- {DOM}
- {/* 其次, 允许将额外补充内容添加到这里 */}
- {this.props.children}
- </p>
- }
- }
- class ChineseName extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- value: ''
- }
- this.verification = this.verification.bind(this)
- }
- verification(e) {
- let v = e.target.value
- // 必须是中文字符
- if (/[\u4e00-\u9fa5]/.test(v)) {
- this.setState({
- value: v
- })
- }
- }
- render() {
- return <BaseInput label={'名字'}
- type={'input'}
- value={this.state.value}
- onChange={this.verification}>
- <span style={{color: 'red'}}>只允许输入中文字符</span>
- </BaseInput>
- }
- }
- class EnglishName extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- value: ''
- }
- this.verification = this.verification.bind(this)
- }
- verification(e) {
- let v = e.target.value
- // 只能是英文字母和空白符
- if (!/[^a-zA-Z\s]/.test(v)) {
- this.setState({
- value: v
- })
- }
- }
- render() {
- return <BaseInput label={'英文名'}
- type={'input'}
- value={this.state.value}
- onChange={this.verification}>
- <span style={{color: 'red'}}>只允许输入 a~z, 或者 A~Z, 或者空白字符</span>
- </BaseInput>
- }
- }
- class TextareaInput extends React.Component {
- render() {
- return <BaseInput label={'个人情况'} type={'textarea'}>
- <span style={{color: 'red'}}>请务必填写</span>
- </BaseInput>
- }
- }
- ReactDOM.render(
- <p>
- <ChineseName/>
- <EnglishName/>
- <TextareaInput/>
- </p>,
- document.getElementById('root')
- )
说明:
BaseInput 是基础组件, 他负责开放接口其他扩展组件只需要关心他开放了哪些接口, 然后使用即可;
ChineseName 是扩展组件之一(是基础组件的特殊实例);
通过 props.type 告诉基础组件, 基础组件负责选择使用哪一个类型的 HTML 元素(input 或者 textarea);
通过 props.value 传值给基础组件, 基础组件负责将这个指放到指定的位置;
传递验证函数 verification 给基础组件, 基础组件负责管理什么时候调用他;
通过 props.children 告诉基础组件, 需要将一些来源于扩展组件的 HTML 元素插入, 而基础组件负责决定插入到哪里;
更好的优化方法:
假如这个基础组件更加复杂, 可以将基础组件再拆分;
比如拆分为 MyInput 组件和 MyTextArea 组件;
基础组件专心于基础组件本身的样式, 以及一些通用逻辑, 以及 props.children 放置在哪里等;
细分后的 MyInput 组件, 可以专心于搞定 input 输入框的样式, 通用逻辑等;
来源: https://www.2cto.com/kf/201802/721138.html