1. 目标一: 先定好api
直接搜索查看 antd layout 的api 点击跳转
那我们就先定好先做 Layout, Header, Sider, Content, Footer 这些API
2. 目标二: 渲染最简单的layout
- 新建文件:
@/lib/Layout/layout.tsx:
import * as React from 'react';
import Header from './components/Header/header';
import Content from './components/Content/content';
import Footer from './components/Footer/footer';
interface propsType {
}
const Layout: React.FC<propsType> = ({children}) => {
return (
<div>
Layout
{children}
</div>
);
};
export {Header, Content, Footer}
export default Layout;
新建文件:@/lib/Layout/components/Sider/sider.tsx
import * as React from 'react';
interface propsType {
}
const Sider: React.FC<propsType> = () => {
return (
<div>Sider</div>
);
};
export default Sider;
新建: @/lib/Layout/components/Header/header.tsx
import * as React from 'react';
interface propsType {
}
const Header: React.FC<propsType> = () => {
return (
<div>Header</div>
);
};
export default Header;
新建: @/lib/Layout/components/Footer/footer.tsx
import * as React from 'react';
interface propsType {
}
const Footer: React.FC<propsType> = () => {
return (
<div>Footer</div>
);
};
export default Footer;
新建: @/lib/Layout/components/Content/content.tsx
import * as React from 'react';
interface propsType {
}
const Content: React.FC<propsType> = () => {
return (
<div>Content</div>
);
};
export default Content;
新建: @/lib/Layout/layout.example.tsx
import * as React from 'react';
import Layout, {Content, Footer, Header} from './layout';
interface propsType {
}
const LayoutExample: React.FC<propsType> = () => {
return (
<Layout>
<Header/>
<Content/>
<Footer/>
</Layout>
);
};
export default LayoutExample;
- 修改:
@/lib/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import LayoutExample from './Layout/layout.example';
ReactDOM.render(
<div>
<LayoutExample/>
</div>
,
document.getElementById('root')
)
- 查看浏览器:http://localhost:8080/
我们的目标实现了!
3. 目标三: 让Layout组件能接受ClassName , Style 属性
- 在
@/lib/Layout/layout.tsx页面, 查看react的声明
- 在react声明文件中搜索 className
- 根据名字猜测觉得: HTMLAttributes更适合Layout
- 修改
@/lib/Layout/layout.tsx:
import * as React from 'react';
import {HTMLAttributes } from 'react';
import classNames from 'classnames';
import Header from './components/Header/header';
import Content from './components/Content/content';
import Footer from './components/Footer/footer';
import {scopedClassMaker} from '../utils';
import './layout.scss'
interface propsType extends HTMLAttributes<HTMLElement>{
}
const sc = scopedClassMaker('sweetui-layout')
const Layout: React.FC<propsType> =
({children,
className,
...restProps}) => {
return (
<div
className={classNames(className,sc())}
{...restProps}
>
Layout
{children}
</div>
);
};
export {Header, Content, Footer}
export default Layout;
修改: @/lib/Layout/layout.example.tsx
import * as React from 'react';
import Layout, {Content, Footer, Header} from './layout';
interface propsType {
}
const LayoutExample: React.FC<propsType> = () => {
return (
<Layout className='wm'>
<Header/>
<Content/>
<Footer/>
</Layout>
);
};
export default LayoutExample;
新建文件:@/lib/Layout/layout.scss
.sweetui-layout{
border: 1px red solid;
width: 100%;
}
- 查看浏览器:
我们的目标实现了
4. 目标四: 实现 antd 各种例子
4.1 改造Header, Content, Footer, Sider组件, 让他们能够接受ClassName等属性
修改:@/lib/Layout/components/Content/content.tsx
import * as React from 'react';
import {HTMLAttributes} from 'react';
import {scopedClassMaker} from '../../../utils';
import classNames from 'classnames';
import './content.scss'
interface propsType extends HTMLAttributes<HTMLElement> {
}
const sc = scopedClassMaker('sweetui-layout');
const Content: React.FC<propsType> =
({
children,
className,
...restProps
}) => {
return (
<div
className={classNames(className, sc('content'))}
{...restProps}
>Content</div>
);
};
export default Content;
修改: @/lib/Layout/components/Content/content.scss
.sweetui-layout-content{
border: 1px blue solid;
width: 100%;
}
修改@/lib/Layout/components/Footer/footer.tsx
import * as React from 'react';
import {HTMLAttributes} from 'react';
import {scopedClassMaker} from '../../../utils';
import './footer.scss'
import classNames from 'classnames';
interface propsType extends HTMLAttributes<HTMLElement> {
}
const sc = scopedClassMaker('sweetui-layout');
const Footer: React.FC<propsType> =
({
children,
className,
...restProps
}) => {
return (
<div
className={classNames(className, sc('footer'))}
{...restProps}
>Footer</div>
);
};
export default Footer;
修改: @/lib/Layout/components/Footer/footer.scss
.sweetui-layout-footer{
border: 1px green solid;
width: 100%;
}
修改:@/lib/Layout/components/Header/header.tsx
import * as React from 'react';
import {HTMLAttributes} from 'react';
import {scopedClassMaker} from '../../../utils';
import './header.scss'
import classNames from 'classnames';
interface propsType extends HTMLAttributes<HTMLElement> {
}
const sc = scopedClassMaker('sweetui-layout');
const Header: React.FC<propsType> =
({
children,
className,
...restProps
}) => {
return (
<div
className={classNames(className, sc('header'))}
{...restProps}
>Header</div>
);
};
export default Header;
修改: @/lib/Layout/components/Header/header.scss
.sweetui-layout-header{
border: 1px pink solid;
width: 100%;
}
修改: @/lib/Layout/components/Sider/sider.tsx
import * as React from 'react';
import {HTMLAttributes} from 'react';
import {scopedClassMaker} from '../../../utils';
import './sider.scss'
import classNames from 'classnames';
interface propsType extends HTMLAttributes<HTMLElement>{
}
const sc = scopedClassMaker('sweetui-layout');
const Sider: React.FC<propsType> =
({
children,
className,
...restProps
}) => {
return (
<div
className={classNames(className, sc('sider'))}
{...restProps}
>Sider</div>
);
};
export default Sider;
修改:@/lib/Layout/components/Sider/sider.scss
.sweetui-layout-sider{
border: 1px blueviolet solid;
width: 100%;
}
4.2 实现第一个例子:
目标:
修改: @/lib/Layout/layout.example.tsx
import * as React from 'react';
import Layout, {Content, Footer, Header} from './layout';
interface propsType {
}
const LayoutExample: React.FC<propsType> = () => {
return (
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Content/>
<Footer style={{height: '50px'}}/>
</Layout>
);
};
export default LayoutExample;
修改: @/lib/Layout/layout.tsx
import * as React from 'react';
import {HTMLAttributes} from 'react';
import classNames from 'classnames';
import Header from './components/Header/header';
import Content from './components/Content/content';
import Footer from './components/Footer/footer';
import {scopedClassMaker} from '../utils';
import './layout.scss';
interface propsType extends HTMLAttributes<HTMLElement> {
}
const sc = scopedClassMaker('sweetui-layout');
const Layout: React.FC<propsType> =
({
children,
className,
...restProps
}) => {
return (
<div
className={classNames(className, sc())}
{...restProps}
>
{children}
</div>
);
};
export {Header, Content, Footer};
export default Layout;
修改: @/lib/Layout/layout.scss
.sweetui-layout{
border: 1px red solid;
width: 100%;
display: flex;
flex-direction: column;
&-content{
flex-grow: 1;
}
}
查看浏览器:
成功实现!
4.3 实现第二个例子:
目标:
修改:@/lib/Layout/layout.example.tsx
import * as React from 'react';
import Layout, {Content, Footer, Header, Sider} from './layout';
interface propsType {
}
const LayoutExample: React.FC<propsType> = () => {
return (
<div>
<h1>------第一个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Content/>
<Footer style={{height: '50px'}}/>
</Layout>
<h1>------第二个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Layout>
<Sider>Sider</Sider>
<Content>Content</Content>
</Layout>
<Footer style={{height: '50px'}}/>
</Layout>
</div>
);
};
export default LayoutExample;
修改@/lib/Layout/layout.tsx
import * as React from 'react';
import {HTMLAttributes, ReactElement } from 'react';
import classNames from 'classnames';
import Header from './components/Header/header';
import Content from './components/Content/content';
import Footer from './components/Footer/footer';
import {scopedClassMaker} from '../utils';
import './layout.scss';
import Sider from './components/Sider/sider';
interface propsType extends HTMLAttributes<HTMLElement> {
children: ReactElement | Array<ReactElement>
}
const sc = scopedClassMaker('sweetui-layout');
const Layout: React.FC<propsType> =
({
children,
className,
...restProps
}) => {
let hasSider = false;
if((children as Array<ReactElement>).length){
(children as Array<ReactElement>).forEach(item =>{
if(item.type === Sider){
hasSider = true;
}
})
}
return (
<div
className={classNames(className, sc(), hasSider && sc('has-sider'))}
{...restProps}
>
{children}
</div>
);
};
export {Header, Content, Footer, Sider};
export default Layout;
修改: @/lib/Layout/layout.scss
.sweetui-layout{
border: 1px red solid;
width: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
&-content{
flex-grow: 1;
}
& &-has-sider{
flex-direction: row;
}
}
修改: @/lib/Layout/components/Sider/sider.scss
.sweetui-layout-sider{
border: 1px blueviolet solid;
}
查看浏览器:
我们成功了!
4.4 实现第三个例子
目标:
import * as React from 'react';
import Layout, {Content, Footer, Header, Sider} from './layout';
interface propsType {
}
const LayoutExample: React.FC<propsType> = () => {
return (
<div>
<h1>------第一个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Content/>
<Footer style={{height: '50px'}}/>
</Layout>
<h1>------第二个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Layout>
<Sider>Sider</Sider>
<Content>Content</Content>
</Layout>
<Footer style={{height: '50px'}}/>
</Layout>
<h1>------第三个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Layout>
<Content>Content</Content>
<Sider>Sider</Sider>
</Layout>
<Footer style={{height: '50px'}}/>
</Layout>
</div>
);
};
export default LayoutExample;
查看浏览器:
成功实现
4.5 实现第四个例子
修改@/lib/Layout/layout.example.tsx
import * as React from 'react';
import Layout, {Content, Footer, Header, Sider} from './layout';
interface propsType {
}
const LayoutExample: React.FC<propsType> = () => {
return (
<div>
<h1>------第一个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Content/>
<Footer style={{height: '50px'}}/>
</Layout>
<h1>------第二个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Layout>
<Sider>Sider</Sider>
<Content>Content</Content>
</Layout>
<Footer style={{height: '50px'}}/>
</Layout>
<h1>------第三个例子------</h1>
<Layout style={{height: '500px'}}>
<Header style={{height: '50px'}}/>
<Layout>
<Content>Content</Content>
<Sider>Sider</Sider>
</Layout>
<Footer style={{height: '50px'}}/>
</Layout>
<h1>------第四个例子------</h1>
<Layout style={{height: '500px'}}>
<Sider>Sider</Sider>
<Layout>
<Header style={{height: '50px'}}/>
<Content>Content</Content>
<Footer style={{height: '50px'}}/>
</Layout>
</Layout>
</div>
);
};
export default LayoutExample;
修改: @/lib/Layout/layout.scss
.sweetui-layout{
border: 1px red solid;
width: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
&-content{
flex-grow: 1;
}
&-has-sider{
flex-direction: row;
}
}
查看浏览器:
我们成功了
4.6 代码优化
修改: @/lib/Layout/layout.tsx
const childrenAsArray = (children as Array<ReactElement>)
const hasSider = childrenAsArray.length &&
childrenAsArray.reduce(
(previousValue, currentNode) =>
previousValue|| currentNode.type === Sider
, false )