一、组件化
1、使用组合思想来实现组件化
在react中组件的复用,是组合还是继承?是组合
2、一个正常的组件有哪些东西
3、字段校验
(1)一般校验两种:必填非必填,是什么类型
(2)PropTypes.element(react元素类型):包含jsx、string、null、undifined
(3)必填属性使用isRequired来约束,非必填属性一般给默认值
(4)所有属性都要给类型约束
(5)PropTypes.oneOf('primary', 'danger', 'info'):取值是其中之一
(6)oneOfType(PropTypes.bool,PropTypes.instanceOf(Array), PropTypes.element):取值类型是其中之一
(7)PropTypes.node:比PropTypes.element范围更广
(8)指定user这个对象类型的结构
user: PropTypes.shape({
name: PropTypes.string.isRequired,
age: PropTypes.number,
})
4、函数命名规则
(1)如果一个函数参与视图渲染,前面不加on
(2)如果一个函数跟事件有关,前面加on
5、什么是render props
(1)在封装React组件时,如果一个props属性其值是函数类型并返回JSX视图结果,那么这个props属性就是render props,即,可以参与组件的视图渲染。例如:<Modal footer={()=>{xxx}}。这个footer属性就被称为render props
(2)render props在组件化中,用的非常多
6、{...props}
属性穿透
父组件给的props,直接给到子组件
7、Protal
(1)protal提供了一种将子节点渲染到存在于父组件以外的DOM节点的方案,比如我要将弹窗挂载到body上
(2)可以把一段JSX结构,"穿梭"插入到父级节点中的一种能力
语法:ReactDOM.createPortal(<jsx/>,dom节点)
8、到底什么是组合,怎么理解组合
组合是一种设计模式,它是React组件化的基础
9、如何封装组件
第一步,根据UI视图拆解结构,得到一个一个可独立封装的小组件
第二步,把这些独立的小部件分别封装
第三步,再使用props把这些小组件串联起来
二、例子
javascript
import '@/assets/modal.scss';
import PropTypes from 'prop-types';
import { useState } from 'react';
import { createPortal } from 'react-dom';
// 封装button组件
function Button (props) {
const { type, children, onClick } = props;
return (
<div className='self-button'>
<span className={type} onClick={onClick}>{children}</span>
</div>
);
}
Button.propTypes = {
type: PropTypes.oneOf(['default', 'primary', 'danger', 'info']),
children: PropTypes.string,
onClick: PropTypes.func,
};
Button.defaultProps = {
type: 'default',
children: '点击按钮',
};
// 封装组件
function Modal (props) {
const { title, closable, children, type, visible, onCancel, onOk, onDelete, footer } = props;
const randerFooter = () => {
switch (type) {
case 'confirm':
return (
<div>
<Button type="primary" onClick={onOk}>确认</Button>
<Button onClick={onCancel}>取消</Button>
</div>
);
case 'danger':
return (
<div>
<Button type="danger" onClick={onDelete}>删除</Button>
<Button onClick={onCancel}>取消</Button>
</div>
);
case 'info':
return (
<div>
<Button type="info" onClick={onOk}>确认</Button>
<Button onClick={onCancel}>取消</Button>
</div>
);
default:
return (
<div>
<Button type='info' onClick={onCancel}>我知道了</Button>
</div>
);
}
};
const handlerLayer = (ev) => {
console.log('---ev.currentTarget', ev.currentTarget.dataset);
console.log('---ev.target', ev.target.dataset);
if (ev.target.dataset.self === 'layer') { // 防止事件冒泡到父元素
onCancel();
}
};
return (
<div
className="layer"
style={{ display: visible ? 'block' : 'none' }}
data-self="layer"
onClick={handlerLayer}>
<div className="modal">
<header>
<div>
<div>{title}</div>
<div onClick={onCancel}>{closable && 'X'}</div>
</div>
</header>
<main>{children}</main>
<footer>
{footer ? footer() : randerFooter()}
</footer>
</div>
</div>
);
}
// 页面组件
function A () {
const [show, setShow] = useState(false);
const submit = () => {
// 异步调接口
setTimeout(() => {
console.log('---submit 异步调接口');
setShow(false);
},1000);
};
return createPortal(
<div>
<button onClick={() => setShow(true)}>测试弹框</button>
<Modal
visible={show} // 对话框是否可见
title={<div>警告</div>} // 对话框标题
closable={true} // 是否可关闭
type='info' // 对话框类型
onCancel={() => setShow(false)} //点击遮罩层或右上角叉或取消按钮的回调
onOk={() => submit()}
onDelete={() => submit()}
footer={() => { // 用户自定义footer
return [
<Button type="primary" key='1'>确认1</Button>,
];
}}
>
用户名:<input type="text" />
</Modal>
</div>,
document.body
);
}
export default A;
// 属性校验
Modal.propTypes = {
title: PropTypes.elementType,
closable: PropTypes.bool,
children: PropTypes.node, // children是Modal组件标签内的内容
// xx: PropTypes.oneOf(['primary', 'danger', 'info']),
// yy: oneOfType([PropTypes.bool,PropTypes.instanceOf(Array), PropTypes.element]),
type: PropTypes.oneOf(['confirm', 'danger', 'info']),
visible: PropTypes.bool,
onCancel: PropTypes.func,
onOk: PropTypes.func,
onDelete: PropTypes.func,
footer: PropTypes.func,
};
// 默认值
Modal.defaultProps = {
title: '标题',
closable: true,
children: <div>请编写主体内容</div>,
type: 'info',
visible: false,
onCancel: () => {},
onOk: () => {},
onDelete: () => {},
};