We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文是React造轮系列第三篇。
1.React 造轮子系列:Icon 组件思路
2.React造轮系列:对话框组件 - Dialog 思路
参考 And Design ,Layout 组件分别分为 Layout, Header, Aside, Content,Footer 五个组件。基本使用结构如下:
Layout
Header
Aside
Content
Footer
<Layout> <Header>header</Header> <Content>content</Content> <Footer>footer</Footer> </Layout>
假如我们想直接在 Layout 组件添加 style 和 className 如:
style
className
<Layout style={{height: 500}} className='hi'> // 同上 </Layout>
这样写并不支持,我们需要在组件内声明它:
// lib/layout/layout.tsx interface Props { style: CSSProperties, className: string } const Layout: React.FunctionComponent<Props> = (props) => { return ( <div className={sc()}> {props.children} </div> ) }
注意这个 style 是一个 CSSProperties,如果不知道 style 是什么类型的,这边有间技巧就是在正常 div 上写 style,然后通过 IDE 功能跳转到定义代码块,就能知道类型了。
div
上面写法看上去没问题,但如果我还想支持 id 或者 src 等 html 原生的属性呢,是不是要一个一个的写呢,当然不是,因为接口是可以继承的,我们直接继承 MapHTMLAttributes 即可:
MapHTMLAttributes
interface Props extends React.MapHTMLAttributes<HTMLElement>{ }
接下就是使用传入的 style, className:
const Layout: React.FunctionComponent<Props> = (props) => { const {className, ...rest} = props return ( <div className={sc(''), className} {...rest}> {props.children} </div> ) }
这里的 sc 是做第一个轮子的时候封装,对应的方法如下:
sc
function scopedClassMaker(prefix: string) { return function x(name?: string) { const result = [prefix, name].filter(Boolean).join('-'); return [result, options && options.extra].filter(Boolean).join(' ') }; } export {scopedClassMaker};
从上述的实现方式,可以发现问题,如果我们直接在组件内写 className={sc(''), className}, 我们通过 sc 方法生成的函数会被传入的 className 覆盖。所以需要就 sc 方法进一步骤改造,扩展传入 className,实现方式如下:
className={sc(''), className}
interface Options { extra: string | undefined } function scopedClassMaker(prefix: string) { return function x(name?: string, options ?:Options ) { const result = [prefix, name].filter(Boolean).join('-'); if (options && options.extra) { return [result, options && options.extra].filter(Boolean).join(' ') } else { return result; } }; } export {scopedClassMaker};
如果懂 Es6 阅读以下代码应该很容易,这里就一在详细讲了。
然后调用方式如下:
// lib/layout/layout.tsx ... const Layout: React.FunctionComponent<Props> = (props) => { const {className, ...rest} = props return ( <div className={sc('', {extra: className})} {...rest}> {props.children} </div> ) } ...
在回顾一下,开始的结构:
//lib/layout/layout.example.tsx <Layout> <Header>header</Header> <Content>content</Content> <Footer>footer</Footer> </Layout>
再次运行:
这里有个问题,实际我们想要的效果是 Content 内容是要撑开的,所以我们需要使用 flex 来布局,自动填写使用的 flex-grow 属性: // lib/layout/layout.scss .gu-layout { border: 1px solid red; display: flex; flex-direction: column; &-content { flex-grow: 1; } }
flex
flex-grow
运行效果:
那如果 Layout 里面还有 Layout 呢,如下:
<h1>第二个例子</h1> <Layout style={{height: 500}}> <Header>header</Header> <Layout> <Aside>aside</Aside> <Content>content</Content> </Layout> <Footer>footer</Footer> </Layout>
如果嵌套 Layout,content 还是没有撑开。说明如果 Layout 里面还有 Layout,那里面的 Layout 应该占满全部。
content
.gu-layout { // 同上 & & { flex-grow: 1; border: 1px solid blue; } }
这里说明一下 & &, & 表示当前的类名,所以就是 & 就是 .gu-layout。
& &
.gu-layout
这样有个问题, 如果 Layout 里面有 Layout,这个里面的一般是左右布局,所以需要设置水平方向为 row
row
& & { flex-grow: 1; border: 1px solid blue; flex-direction: row; }
如果想让 Aside 换到右边,只需要调整位置即可。
<h1>第三个例子</h1> <Layout style={{height: 500}}> <Header>header</Header> <Layout> <Content>content</Content> <Aside>aside</Aside> </Layout> <Footer>footer</Footer> </Layout>
在来看别外一种布局:
<h1>第四个例子</h1> <Layout style={{height: 500}}> <Aside>aside</Aside> <Layout> <Header>header</Header> <Content>content</Content> <Footer>footer</Footer> </Layout> </Layout>
可以看到 我们希望当有 Aside 组件时,需要的是左右布局,当前的样式无法满足,需要再次调整,参考 AntD 设计,当有里面有 Aside 组件, Layout 就多了一个左右布局的样式的 className,所以我们要在 Layout 组件检测 children 类型。
children
实现思路是,可以先在 Layout 组件内打印 children :
所以我可以通过遍历 children 来判断,实现如下:
props.children.map(node => { console.log(node) })
这边不能直接使用 map,因为 children 的类型有5种, ReactChild, ReactFragment ,ReactPortal,boolean, null, undefined,所以这里需要对 children 进行约束,至少要有一个元素。
ReactChild
ReactFragment
ReactPortal
boolean
null
undefined
// lib/layout/layout.tsx interface Props extends React.MapHTMLAttributes<HTMLElement>{ children: ReactElement | Array<ReactElement> } const Layout: React.FunctionComponent<Props> = (props) => { const {className, ...rest} = props let hasAside = false if ((props.children as Array<ReactElement>).length) { (props.children as Array<ReactElement>).map(node => { if (node.type === Aside) { hasAside = true } }) } return ( <div className={sc('', {extra: [className, hasAside && 'hasAside'].join(' ')})} {...rest}> {props.children} </div> ) } export default Layout
添加对应的 css:
.gu-layout { ... &.hasAside { flex-direction: row; .gu-layout{ flex-direction: column } } ... }
上述写法,有些问题,这一个就是使用到了 let 声明,这们就不符合我们函数式编程了,第二个 sc 方法还需要进一步改善。
let
在上述代码中,我们使用了一个 let hasAside = false,来判断 Layout 里面是否有 Aside,这样写就不符合我们函数式的定义了。
let hasAside = false
其实我们做的是通过遍历,然后一个一个判断是否有 Aside ,如果有刚设置为 true, 从上图可以看出,我们最后可以把所有判断结果 或(|)起来,如果为 true ,则有,否则无。这时候我们就可以使用 es6 新引入的 reduce 方法了。
true
或(|)
reduce
// lib/layout/layout.tsx ... const Layout: React.FunctionComponent<Props> = (props) => { const {className, ...rest} = props if ((props.children as Array<ReactElement>).length) { const hasAside = (props.children as Array<ReactElement>) .reduce((result, node) => result || node.type === Aside, false) } return ( <div className={sc('', {extra: [className, hasAside && 'hasAside'].join(' ')})} {...rest}> {props.children} </div> ) } ...
通过 reduce 改进后的方法有个问题,我们 hasAside 是在 if 块域里面的,外部访问不到,那有没有什么办法删除 {} 块作用域呢?
hasAside
if
{}
我们把把 if 条件通过 && 放到跟遍历同一级:
&&
// lib/layout/layout.tsx ... const children = props.children as Array<ReactElement> const hasAside = ( children.length) && children.reduce((result, node) => result || node.type === Aside, false) ...
Layout 组件相对简单,这边主要介绍一些实现思路,源码已经到这里。
参考
《方应杭老师的React造轮子课程》
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
本文是React造轮系列第三篇。
1.React 造轮子系列:Icon 组件思路
2.React造轮系列:对话框组件 - Dialog 思路
初始化 Layout
参考 And Design ,Layout 组件分别分为
Layout
,Header
,Aside
,Content
,Footer
五个组件。基本使用结构如下:假如我们想直接在
Layout
组件添加style
和className
如:这样写并不支持,我们需要在组件内声明它:
注意这个
style
是一个 CSSProperties,如果不知道 style 是什么类型的,这边有间技巧就是在正常div
上写style
,然后通过 IDE 功能跳转到定义代码块,就能知道类型了。上面写法看上去没问题,但如果我还想支持 id 或者 src 等 html 原生的属性呢,是不是要一个一个的写呢,当然不是,因为接口是可以继承的,我们直接继承
MapHTMLAttributes
即可:接下就是使用传入的 style, className:
这里的
sc
是做第一个轮子的时候封装,对应的方法如下:从上述的实现方式,可以发现问题,如果我们直接在组件内写
className={sc(''), className}
, 我们通过sc
方法生成的函数会被传入的className
覆盖。所以需要就 sc 方法进一步骤改造,扩展传入className
,实现方式如下:如果懂 Es6 阅读以下代码应该很容易,这里就一在详细讲了。
然后调用方式如下:
在回顾一下,开始的结构:
再次运行:
这里有个问题,实际我们想要的效果是 Content 内容是要撑开的,所以我们需要使用
flex
来布局,自动填写使用的flex-grow
属性:// lib/layout/layout.scss
.gu-layout {
border: 1px solid red;
display: flex;
flex-direction: column;
&-content {
flex-grow: 1;
}
}
运行效果:
那如果
Layout
里面还有Layout
呢,如下:运行效果:
如果嵌套
Layout
,content
还是没有撑开。说明如果 Layout 里面还有 Layout,那里面的 Layout 应该占满全部。这里说明一下
& &
, & 表示当前的类名,所以就是 & 就是.gu-layout
。运行效果:
这样有个问题, 如果 Layout 里面有
Layout
,这个里面的一般是左右布局,所以需要设置水平方向为row
运行效果:
如果想让 Aside 换到右边,只需要调整位置即可。
运行效果:
在来看别外一种布局:
运行效果:
可以看到 我们希望当有
Aside
组件时,需要的是左右布局,当前的样式无法满足,需要再次调整,参考 AntD 设计,当有里面有Aside
组件, Layout 就多了一个左右布局的样式的className
,所以我们要在 Layout 组件检测children
类型。实现思路是,可以先在 Layout 组件内打印
children
:所以我可以通过遍历
children
来判断,实现如下:这边不能直接使用 map,因为 children 的类型有5种,
ReactChild
,ReactFragment
,ReactPortal
,boolean
,null
,undefined
,所以这里需要对 children 进行约束,至少要有一个元素。添加对应的 css:
运行效果:
上述写法,有些问题,这一个就是使用到了
let
声明,这们就不符合我们函数式编程了,第二个sc
方法还需要进一步改善。删除代码里的 let
在上述代码中,我们使用了一个
let hasAside = false
,来判断Layout
里面是否有Aside
,这样写就不符合我们函数式的定义了。其实我们做的是通过遍历,然后一个一个判断是否有 Aside ,如果有刚设置为
true
, 从上图可以看出,我们最后可以把所有判断结果或(|)
起来,如果为true
,则有,否则无。这时候我们就可以使用 es6 新引入的reduce
方法了。通过 reduce 改进后的方法有个问题,我们
hasAside
是在if
块域里面的,外部访问不到,那有没有什么办法删除{}
块作用域呢?我们把把
if
条件通过&&
放到跟遍历同一级:总结
Layout 组件相对简单,这边主要介绍一些实现思路,源码已经到这里。
参考
《方应杭老师的React造轮子课程》
交流
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered: