# Wiki源代码线性布局组件与占位组件

由 Qiongpan Ke 于 2024-12-02 最后修改
导出
Office格式
以PDF格式导出 以ODT格式导出 以RTF格式导出
其他格式

选择要导出的页面：

选择所有/ 无

Legend:
  已创建页面
  修改后的扩展页
  Clean Extension Page

以HTML格式导出

显示最后作者

author | version | line-number | content
--- | --- | --- | ---
|  | 1 | 第一次开发前端项目，也是第一次使用 React 开发，虽说都是 flex 布局，但依然调样式调到想吐。
|  | 2 |
|  | 3 | 没办法，根据以往开发 APP 的经验，参考 Android 和 HarmonyOS 的线性布局概念，用 React 实现了一个线性布局组件 LinearLayout ，配合占位组件 Placeholder ，可以在不写任行一行样式代码的情况下编排出想要的布局。
|  | 4 |
|  | 5 | = 使用示例 =
|  | 6 |
|  | 7 | 以编排卡片布局为例，左边一个居中的头像，右边上下为主标题与副标题，中间带有分隔空间。
|  | 8 |
|  | 9 | [[image:demo.png]]
|  | 10 |
|  | 11 | 代码如下：
|  | 12 |
|  | 13 | {{code language="html"}}
|  | 14 | <Placeholder height="128px" color="lightgray">
|  | 15 | <Row>
|  | 16 | <Compact align="center"><Placeholder alt="头像" width="48px" height="48px" /></Compact>
|  | 17 | <Blank/>
|  | 18 | <Stretch>
|  | 19 | <Column>
|  | 20 | <Stretch><Placeholder alt="标题" /></Stretch>
|  | 21 | <Blank/>
|  | 22 | <Stretch><Placeholder alt="副标题" /></Stretch>
|  | 23 | </Column>
|  | 24 | </Stretch>
|  | 25 | </Row>
|  | 26 | </Placeholder>
|  | 27 | {{/code}}
|  | 28 |
|  | 29 | = 组件源码 =
|  | 30 |
|  | 31 | linearLayout/index.tsx
|  | 32 |
|  | 33 | {{code language="ts"}}
|  | 34 | import React from "react";
|  | 35 | import styles from "./index.module.less";
|  | 36 |
|  | 37 | /**
|  | 38 | * 线性布局，以行 Row 和列 Column 的嵌套组合来布局界面，行/列内则以紧凑 Compact 和延展 Stretch 来划分空间。
|  | 39 | * 可以以比较简单直接的方式来编排界面元素，甚至配合 Placeholder 组件，可以在不写任何一行 CSS 样式的情况下，
|  | 40 | * 快速编排出需要的界面布局，再在此基础上进行精细的 CSS 样式设置，所以对新手较为友好，老手也可以降低心智负担。
|  | 41 | *
|  | 42 | * // 以编排卡片布局为例，左边一个居中的头像，右边上下为主标题与副标题，中间带有分隔空间。
|  | 43 | * <Placeholder height="128px" color="lightgray">
|  | 44 | * <Row>
|  | 45 | * <Compact align="center"><Placeholder alt="头像" width="48px" height="48px" /></Compact>
|  | 46 | * <Blank/>
|  | 47 | * <Stretch>
|  | 48 | * <Column>
|  | 49 | * <Stretch><Placeholder alt="标题" /></Stretch>
|  | 50 | * <Blank/>
|  | 51 | * <Stretch><Placeholder alt="副标题" /></Stretch>
|  | 52 | * </Column>
|  | 53 | * </Stretch>
|  | 54 | * </Row>
|  | 55 | * </Placeholder>
|  | 56 | *
|  | 57 | * 上述的 Row/Column/Compact/Stretch 等组件，透传了 children/className/style 属性，除了其自身拥有的特性，
|  | 58 | * 可以看作是普通的 <div> 标签，甚至通过 className/sytle 也可以修改这些组件的特性。
|  | 59 | */
|  | 60 | export namespace LinearLayout {
|  | 61 | type TProps = {
|  | 62 | children?: React.ReactNode;
|  | 63 | className?: string;
|  | 64 | style?: React.CSSProperties;
|  | 65 | };
|  | 66 |
|  | 67 | const joinClasses = (...classNames: (string | undefined)[]) => {
|  | 68 | return (classNames ?? [])
|  | 69 | .map(it => it ?? '')
|  | 70 | .filter(it => it.length > 0)
|  | 71 | .join(' ')
|  | 72 | .trim();
|  | 73 | };
|  | 74 |
|  | 75 | const joinStyles = (...styleObjects: (React.CSSProperties | undefined)[]) => {
|  | 76 | return Object.assign({}, ...styleObjects);
|  | 77 | }
|  | 78 |
|  | 79 | type TRowProps = TProps & {
|  | 80 | justify?: string; // 水平方向的对齐方式，相当于投映 justify-content 属性。
|  | 81 | }
|  | 82 |
|  | 83 | /**
|  | 84 | * 单行容器，配合 Compact/Stretch 等子元素，在水平方向上编排界面布局。
|  | 85 | */
|  | 86 | export const Row: React.FC<TRowProps> = (props) => {
|  | 87 | return <div className={
|  | 88 | joinClasses(styles.base, styles.row, props.className)
|  | 89 | } style={
|  | 90 | joinStyles({
|  | 91 | justifyContent: props.justify
|  | 92 | }, props.style)
|  | 93 | }>
|  | 94 | {props.children}
|  | 95 | </div>;
|  | 96 | };
|  | 97 |
|  | 98 | type TColumnProps = TProps & {
|  | 99 | justify?: string; // 垂直方向的对齐方式，相当于投映 justify-content 属性。
|  | 100 | }
|  | 101 |
|  | 102 | /**
|  | 103 | * 单列容器，配合 Compact/Stretch 等子元素，在垂直方向上编排界面布局。
|  | 104 | */
|  | 105 | export const Column: React.FC<TColumnProps> = (props) => {
|  | 106 | return <div className={
|  | 107 | joinClasses(styles.base, styles.column, props.className)
|  | 108 | } style={props.style}>
|  | 109 | {props.children}
|  | 110 | </div>;
|  | 111 | };
|  | 112 |
|  | 113 | type TCompactProps = TProps & {
|  | 114 | align?: string; // 当前紧凑容器在所处线性布局方向上的对齐方式，相当于投映 align-self 属性。
|  | 115 | }
|  | 116 |
|  | 117 | /**
|  | 118 | * 紧凑容器，在水平和垂直方向均紧凑粘合子元素内容。
|  | 119 | */
|  | 120 | export const Compact: React.FC<TCompactProps> = (props) => {
|  | 121 | return <div className={
|  | 122 | joinClasses(styles.base, styles.compact, props.className)
|  | 123 | } style={
|  | 124 | joinStyles({
|  | 125 | alignSelf: props.align
|  | 126 | }, props.style)
|  | 127 | }>{props.children}</div>;
|  | 128 | };
|  | 129 |
|  | 130 | type TStretchProps = TProps & {
|  | 131 | weight?: string; // 延伸比重（类似 Android 中的 layoutWeight 属性，默认值为 1 ），相当于投映 flex-grow 属性。
|  | 132 | align?: string; // 各子元素（默认为水平方向排布）在垂直方向的对齐方式（上中下等），相当于投映 align-items 属性。
|  | 133 | justify?: string; // 各子元素（默认为水平方向排布）在水平方向的堆叠方式（左中右等），相当于投映 justify-content 属性。
|  | 134 | };
|  | 135 |
|  | 136 | /**
|  | 137 | * 延展容器，在水平和垂直方向均贪婪延展，多个 Stretch 容器按比重 weight 占用父节点的剩余空间，默认为平分。
|  | 138 | */
|  | 139 | export const Stretch: React.FC<TStretchProps> = (props) => {
|  | 140 | return <div className={
|  | 141 | joinClasses(styles.base, styles.stretch, props.className)
|  | 142 | } style={
|  | 143 | joinStyles({
|  | 144 | flexGrow: props.weight,
|  | 145 | alignItems: props.align,
|  | 146 | justifyContent: props.justify,
|  | 147 | }, props.style)
|  | 148 | }>
|  | 149 | {props.children}
|  | 150 | </div>;
|  | 151 | };
|  | 152 |
|  | 153 | type TBlankProps = TProps & {
|  | 154 | length?: string; // 所处线性布局方向上的长度，默认为 8px 。
|  | 155 | };
|  | 156 |
|  | 157 | /**
|  | 158 | * 空白内容，在所处线性布局方向上的占用空间长度的空间，默认占用 8px 。
|  | 159 | */
|  | 160 | export const Blank: React.FC<TBlankProps> = (props) => {
|  | 161 | return <div className={
|  | 162 | joinClasses(styles.base, styles.blank, props.className)
|  | 163 | } style={
|  | 164 | joinStyles({
|  | 165 | flexBasis: props.length
|  | 166 | }, props.style)
|  | 167 | } />;
|  | 168 | }
|  | 169 | };
|  | 170 |
|  | 171 | // 与命名空间 LinearLayout 一并导出，方便不带命名空间直接使用。
|  | 172 | export const Row = LinearLayout.Row;
|  | 173 | export const Column = LinearLayout.Column;
|  | 174 | export const Compact = LinearLayout.Compact;
|  | 175 | export const Stretch = LinearLayout.Stretch;
|  | 176 | export const Blank = LinearLayout.Blank;
|  | 177 | {{/code}}
|  | 178 |
|  | 179 | linearLayout/index.module.less
|  | 180 |
|  | 181 | {{code language="less"}}
|  | 182 | .base {
|  | 183 | display: flex;
|  | 184 | }
|  | 185 |
|  | 186 | .row {
|  | 187 | flex-direction: row;
|  | 188 | flex: 1 1 0px;
|  | 189 | align-self: stretch;
|  | 190 | }
|  | 191 |
|  | 192 | .column {
|  | 193 | flex-direction: column;
|  | 194 | flex: 1 1 0px;
|  | 195 | align-self: stretch;
|  | 196 | }
|  | 197 |
|  | 198 | .compact {
|  | 199 | flex: unset 0 0px;
|  | 200 | align-self: flex-start;
|  | 201 | }
|  | 202 |
|  | 203 | .stretch {
|  | 204 | flex: 1 1 0px;
|  | 205 | align-self: stretch;
|  | 206 | }
|  | 207 |
|  | 208 | .blank {
|  | 209 | flex: 0 0 8px;
|  | 210 | }
|  | 211 | {{/code}}
|  | 212 |
|  | 213 | placeholder/index.tsx
|  | 214 |
|  | 215 | {{code language="ts"}}
|  | 216 | import React from "react";
|  | 217 | import styles from "./index.module.less";
|  | 218 |
|  | 219 | enum EArea {
|  | 220 | NONE = 'none',
|  | 221 | WIDE = 'wide',
|  | 222 | FULL = 'full'
|  | 223 | }
|  | 224 |
|  | 225 | type TProps = {
|  | 226 | children?: React.ReactNode,
|  | 227 | className?: string;
|  | 228 | style?: React.CSSProperties;
|  | 229 | width?: string,
|  | 230 | height?: string,
|  | 231 | area?: string | EArea,
|  | 232 | align?: string,
|  | 233 | color?: string,
|  | 234 | borderd?: boolean,
|  | 235 | alt?: string
|  | 236 | }
|  | 237 |
|  | 238 | export const Placeholder: React.FC<TProps> = (props) => {
|  | 239 | return (
|  | 240 | <div className={
|  | 241 | (props.className ?? '') + ' ' + [
|  | 242 | styles.component,
|  | 243 | styles[`area-${props.area ?? 'full'}`]
|  | 244 | ].join(' ')
|  | 245 | } style={{
|  | 246 | width: props.width,
|  | 247 | height: props.height,
|  | 248 | backgroundColor: props.color,
|  | 249 | alignSelf: props.align,
|  | 250 | ...props.style
|  | 251 | }}>
|  | 252 | {props.children ?? props.alt ?? 'Placeholder'}
|  | 253 | </div>
|  | 254 | )
|  | 255 | };
|  | 256 | {{/code}}
|  | 257 |
|  | 258 | placeholder/index.module.less
|  | 259 |
|  | 260 | {{code language="less"}}
|  | 261 | .component {
|  | 262 | display: flex;
|  | 263 | border: 1px dashed black;
|  | 264 | }
|  | 265 |
|  | 266 | .area-none {
|  | 267 | flex: unset 0 0px;
|  | 268 | align-self: flex-start;
|  | 269 | }
|  | 270 |
|  | 271 | .area-wide {
|  | 272 | flex: 1 0 0px;
|  | 273 | align-self: flex-start;
|  | 274 | }
|  | 275 |
|  | 276 | .area-full {
|  | 277 | flex: 1 0 0px;
|  | 278 | align-self: stretch;
|  | 279 | }
|  | 280 | {{/code}}

# 同级页面

# 博客类别

- https://keqiongpan.cn:80/bin/view/Blog/">全部

- News (0)
- Other (0)
- Personal (0)

# 当前已创建的

- 线性布局组件与占位组件
- React
- CSS Flexbox Cheat Sheet
- CSS界面布局控制属性概述
- CSS

粤公网安备 44011802000481号 粤ICP备2020117634号
版权所有，如发现盗用模仿必追诉法律责任！
CopyRight © 2020-2023 keqiongpan.cn. All Right Reserved.