React(12)组合(类似 Vue 组件的插槽)和继承
2018-02-12 18:18
603 查看
18、组合(类似 Vue 组件的插槽)
在Vue中,假如我们需要让子组件的一部分内容,被父组件控制,而不是被子组件控制,那么我们会采用插槽的写法<slot></slot>
在 React 里也有类似的写法,父组件写法是相同的,但子组件是采用
{this.props.children}来实现。
示例:
class MyChild extends React.Component { render() { return <div> {this.props.children} </div> } } 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 <div> <MyChild> {this.state.date} </MyChild> </div> } }
如上代码,子组件展现的内容,是父组件中,子组件标签内含的内容。
关于
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> <div> abc <span>{this.state.date}</span> </div> </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 <div> 父元素的第一个字符串:{this.props.children.props.children[0]} <br/> 父元素的第二个字符串:{this.props.children.props.children[1]} </div> } } 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 <div> <MyChild> <div> {this.state.date} {this.state.year} </div> </MyChild> </div> } }
当然,如果是纯字符串的话,通过变量 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 <div style={{height: '50px'}}> {DOM} {/* 其次,允许将额外补充内容添加到这里 */} {this.props.children} </div> } } 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( <div> <ChineseName/> <EnglishName/> <TextareaInput/> </div>, document.getElementById('root') )
说明:
BaseInput 是基础组件,他负责开放接口。其他扩展组件只需要关心他开放了哪些接口,然后使用即可;
ChineseName 是扩展组件之一(是基础组件的特殊实例);
通过 props.type 告诉基础组件,基础组件负责选择使用哪一个类型的 HTML 元素(input 或者 textarea);
通过 props.value 传值给基础组件,基础组件负责将这个指放到指定的位置;
传递验证函数
verification给基础组件,基础组件负责管理什么时候调用他;
通过 props.children 告诉基础组件,需要将一些来源于扩展组件的 HTML 元素插入,而基础组件负责决定插入到哪里;
更好的优化方法:
假如这个基础组件更加复杂,可以将基础组件再拆分;
比如拆分为 MyInput 组件和 MyTextArea 组件;
基础组件专心于基础组件本身的样式,以及一些通用逻辑,以及 props.children 放置在哪里等;
细分后的 MyInput 组件,可以专心于搞定 input 输入框的样式,通用逻辑等;
相关文章推荐
- #003 React 组件 继承 自定义的组件
- Vue 作用域插槽 列表组件 示例
- vue动态组件和slot插槽
- React为 Vue 引入容器组件和展示组件的教程详解
- React文档(十二)组合vs继承
- Vuejs——(12)组件——动态组件
- React(0.13) 组件的组合使用
- React组件重构之嵌套+继承及高阶组件详解
- react 入门-创建组件(1)继承component法
- React 组件转 Vue 组件的命令写法
- Vue 组件3 作用域插槽
- React学习之组合和继承(十)
- Vue 组件和插槽的理解与使用
- VUX v2.1.1-rc.12 发布,基于 WeUI 的 Vue 移动端组件库
- React(8)条件渲染(类似 Vue 的 v-if)
- 使用Vue的slot插槽分发父组件内容实现高度复用、更加灵活的组件
- React.js 官方资料摘记:组合 VS 继承
- React组件继承的由来
- VUX v2.1.1-rc.12 发布,基于 WeUI 的 Vue 移动端组件库
- Vue与React的异同-组件(二)