资讯专栏INFORMATION COLUMN

React造轮系列:Layout 组件思路

neroneroffy / 1153人阅读

摘要:本文是造轮系列第三篇。造轮子系列组件思路造轮系列对话框组件思路想阅读更多优质文章请猛戳博客一年百来篇优质文章等着你初始化参考组件分别分为五个组件。参考方应杭老师的造轮子课程交流干货系列文章汇总如下,觉得不错点个,欢迎加群互相学习。

本文是React造轮系列第三篇。

1.React 造轮子系列:Icon 组件思路

2.React造轮系列:对话框组件 - Dialog 思路

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

初始化 Layout

参考 And Design ,Layout 组件分别分为 Layout, Header, Aside, Content,Footer 五个组件。基本使用结构如下:


  
header
content
footer

假如我们想直接在 Layout 组件添加 styleclassName 如:


 // 同上

这样写并不支持,我们需要在组件内声明它:

// lib/layout/layout.tsx
interface Props {
  style: CSSProperties,
  className: string
}

const Layout: React.FunctionComponent = (props) => {
  return (
    
{props.children}
) }

注意这个 style 是一个 CSSProperties,如果不知道 style 是什么类型的,这边有间技巧就是在正常 div 上写 style,然后通过 IDE 功能跳转到定义代码块,就能知道类型了。

上面写法看上去没问题,但如果我还想支持 id 或者 src 等 html 原生的属性呢,是不是要一个一个的写呢,当然不是,因为接口是可以继承的,我们直接继承 MapHTMLAttributes 即可:

interface Props extends React.MapHTMLAttributes{
}

接下就是使用传入的 style, className:

const Layout: React.FunctionComponent = (props) => {
  const {className, ...rest} = props
  return (
    
{props.children}
) }

这里的 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,实现方式如下:

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) => {
  const {className, ...rest} = props
  return (
    
{props.children}
) } ...

在回顾一下,开始的结构:

//lib/layout/layout.example.tsx

  
header
content
footer

再次运行:

这里有个问题,实际我们想要的效果是 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 呢,如下:

第二个例子

header
content
footer

运行效果:

如果嵌套 Layoutcontent 还是没有撑开。说明如果 Layout 里面还有 Layout,那里面的 Layout 应该占满全部。

.gu-layout {
  // 同上
  & & {
    flex-grow: 1;
    border: 1px solid blue;
  }
}

这里说明一下 & &, & 表示当前的类名,所以就是 & 就是 .gu-layout

运行效果:

这样有个问题, 如果 Layout 里面有 Layout,这个里面的一般是左右布局,所以需要设置水平方向为 row

  & & {
    flex-grow: 1;
    border: 1px solid blue;
    flex-direction: row;
  }

运行效果:

如果想让 Aside 换到右边,只需要调整位置即可。

第三个例子

header
content
footer

运行效果:

在来看别外一种布局:

第四个例子

header
content
footer

运行效果:

可以看到 我们希望当有 Aside 组件时,需要的是左右布局,当前的样式无法满足,需要再次调整,参考 AntD 设计,当有里面有 Aside 组件, Layout 就多了一个左右布局的样式的 className,所以我们要在 Layout 组件检测 children 类型。

实现思路是,可以先在 Layout 组件内打印 children

所以我可以通过遍历 children 来判断,实现如下:

props.children.map(node => {
  console.log(node)
})

这边不能直接使用 map,因为 children 的类型有5种, ReactChild, ReactFragment ,ReactPortal,boolean, null, undefined,所以这里需要对 children 进行约束,至少要有一个元素。

// lib/layout/layout.tsx

interface Props extends React.MapHTMLAttributes{
  children: ReactElement | Array 
}

const Layout: React.FunctionComponent = (props) => {
  const {className, ...rest} = props
  let hasAside = false
  if ((props.children as Array).length) {
    (props.children as Array).map(node => {
      if (node.type === Aside) {
        hasAside = true
      }
    })
  }
  return (
    
{props.children}
) } export default Layout

添加对应的 css:

.gu-layout {
  ...
  &.hasAside {
    flex-direction: row;
    .gu-layout{
      flex-direction: column
    }
  }
  ...
}

运行效果:

上述写法,有些问题,这一个就是使用到了 let 声明,这们就不符合我们函数式编程了,第二个 sc 方法还需要进一步改善。

删除代码里的 let

在上述代码中,我们使用了一个 let hasAside = false,来判断 Layout 里面是否有 Aside,这样写就不符合我们函数式的定义了。

其实我们做的是通过遍历,然后一个一个判断是否有 Aside ,如果有刚设置为 true, 从上图可以看出,我们最后可以把所有判断结果 或(|)起来,如果为 true ,则有,否则无。这时候我们就可以使用 es6 新引入的 reduce 方法了。

// lib/layout/layout.tsx

...
const Layout: React.FunctionComponent = (props) => {
  const {className, ...rest} = props
  if ((props.children as Array).length) {
    const hasAside = (props.children as Array)
      .reduce((result, node) => result || node.type === Aside, false)
  }
  return (
    
{props.children}
) } ...

通过 reduce 改进后的方法有个问题,我们 hasAside 是在 if 块域里面的,外部访问不到,那有没有什么办法删除 {} 块作用域呢?

我们把把 if 条件通过 && 放到跟遍历同一级:

// lib/layout/layout.tsx
...
  const children = props.children as Array
  const hasAside = ( children.length)
    && children.reduce((result, node) => result || node.type === Aside, false)

...
总结

Layout 组件相对简单,这边主要介绍一些实现思路,源码已经到这里。

参考

《方应杭老师的React造轮子课程》

交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/104315.html

相关文章

  • React造轮系列:对话框组件 - Dialog 思路

    摘要:本文是造轮系列第二篇。实现方式事件处理跟差不多,唯一多了一步就是当点击或者的时候,如果外部有回调就需要调用对应的回调函数。 本文是React造轮系列第二篇。 1.React 造轮子系列:Icon 组件思路 本轮子是通过 React + TypeScript + Webpack 搭建的,至于环境的搭建这边就不在细说了,自己动手谷歌吧。当然可以参考我的源码。 想阅读更多优质文章请猛戳Git...

    qianfeng 评论0 收藏0
  • 基本功 | Litho的使用及原理剖析

    摘要:,解绑视图,主要用于重置视图的数据相关的属性,防止出现复用问题。的特性及原理剖析官网首页通过个段落重点介绍了的个特性。 1. 什么是Litho? Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提升RecyclerView复杂列表的滑动性能和降低内存占用。下面是Litho官网的介绍: Litho is a declarative framewo...

    phpmatt 评论0 收藏0
  • 动手造轮 | 一款Bash 脚本 +HTTP 请求工具 +JSON 解析工具组成的测试小工具

    摘要:数人云开源一款容器管理工具,开发过程中,为了保证的健壮性和稳定性,数人云开发团队自制了一套适合测试的小工具。于是一款简单的脚本请求工具解析工具组成的测试小工具雏形出现了。是一款命令行解析文本的工具,支持非常多的语法解析构造重组文本。 数人云开源一款容器管理工具Crane,Crane开发过程中,为了保证API的健壮性和稳定性, 数人云开发团队自制了一套适合Crane API测试的小工具。...

    hyuan 评论0 收藏0
  • 浅说虚拟列表的实现原理

    摘要:虚拟列表的实现有多种方案,本文以组件为基础进行分析。常见的无限滚动便是延迟渲染的一种实现,而虚拟列表则是按需渲染的一种实现。接下来,本文会简单介绍虚拟列表的一种实现方案。实现本章节将会创建一个组件,并结合代码,慢慢梳理虚拟列表的实现。 在 列表数据的展示优化 一文中,提到了对于列表形态的数据展示的按需渲染。这种方式是指根据容器元素的高度以及列表项元素的高度来显示长列表数据中的某一个部分...

    赵春朋 评论0 收藏0
  • B站Up主-山地人-这位老哥2019年的前端自学计划进展如何?——讲一个B站Up主自学前端85天的故

    摘要:前言自从上次在掘金发布年山地人的前端完整自学计划讲一个站主山地人的天前端自学故事以来,一眨眼山地人老哥在站做主已经有天了。所以这个体系里的一些框架包括也是山地人年自学计划的一部分。月底,山地人老哥开启了的两个专题。 前言 自从上次在掘金发布【2019年山地人的前端完整自学计划——讲一个B站UP主山地人的40天前端自学故事】 以来,一眨眼山地人老哥在B站做Up主已经有85天了。 时隔一个...

    cocopeak 评论0 收藏0

发表评论

0条评论

neroneroffy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<