组件
组件是 Purepy 的核心概念之一,它允许你将 UI 拆分成独立、可重用的部分。本指南将介绍如何创建和使用组件。
什么是组件?
在 Purepy 中,组件是返回元素结构的函数。组件接收参数(称为"props"),并返回描述界面的元素。
基本组件
创建组件
组件是一个接收 props 参数并返回元素的函数:
python
from pure.html import div, h2, p
def Card(props):
title = props.get('title', '')
content = props.get('content', '')
return div(
h2(title).class_name('card-title'),
p(content).class_name('card-content')
).class_name('card')使用组件
python
# 使用组件
my_card = Card({
'title': '欢迎使用 Purepy',
'content': '这是一个基于 Python 的模板引擎'
})
# 渲染组件
my_card.to_print()输出:
html
<div class="card">
<h2 class="card-title">欢迎使用 Purepy</h2>
<p class="card-content">这是一个基于 Python 的模板引擎</p>
</div>组件设计模式
1. 单一职责
每个组件应该只做一件事:
python
# 不好的做法:一个组件做太多事情
def Page(props):
return div(
# 头部
div(
h1('网站标题'),
nav(
a('首页').href('/'),
a('关于').href('/about'),
a('联系').href('/contact')
)
).class_name('header'),
# 内容
div(
h2(props.get('title')),
p(props.get('content'))
).class_name('content'),
# 页脚
div(
p('版权所有 © 2024')
).class_name('footer')
)
# 好的做法:拆分成多个组件
def Header():
return div(
h1('网站标题'),
nav(
a('首页').href('/'),
a('关于').href('/about'),
a('联系').href('/contact')
)
).class_name('header')
def Content(props):
return div(
h2(props.get('title')),
p(props.get('content'))
).class_name('content')
def Footer():
return div(
p('版权所有 © 2024')
).class_name('footer')
def Page(props):
return div(
Header(),
Content(props),
Footer()
)2. 组合优于继承
使用组合来构建复杂组件:
python
def Button(props):
variant = props.get('variant', 'primary')
size = props.get('size', 'medium')
text = props.get('text', '')
return button(text).class_name(f'btn btn-{variant} btn-{size}')
def Card(props):
title = props.get('title', '')
content = props.get('content', '')
return div(
h2(title),
p(content),
Button({
'text': '了解更多',
'variant': 'secondary',
'size': 'small'
})
).class_name('card')3. 容器组件
创建可以包含其他内容的组件:
python
def Container(props):
children = props.get('children', [])
return div(
*children
).class_name('container')
# 使用
Container({
'children': [
h1('标题'),
p('内容'),
button('点击')
]
})组件通信
1. 通过 Props 传递数据
从父组件向子组件传递数据:
python
def Parent():
data = {
'title': '标题',
'content': '内容'
}
return div(
Child(data)
)
def Child(props):
return div(
h2(props.get('title')),
p(props.get('content'))
)2. 组件组合
通过组合组件实现复杂的 UI:
python
def Layout(props):
return div(
Header(props.get('header', {})),
main(props.get('children', [])),
Footer()
).class_name('layout')
def App():
return Layout({
'header': {
'title': '我的应用',
'nav_items': [
{'text': '首页', 'url': '/'},
{'text': '关于', 'url': '/about'}
]
},
'children': [
h1('欢迎'),
p('这是主页内容')
]
})条件渲染
根据条件渲染不同的内容:
python
def UserGreeting(props):
user = props.get('user')
is_admin = props.get('is_admin', False)
if not user:
return div(
p('请登录')
).class_name('guest-message')
return div(
h2(f'欢迎,{user["name"]}!'),
p('管理员控制面板') if is_admin else p('用户面板')
).class_name('user-message')列表渲染
渲染列表数据:
python
def TodoList(props):
todos = props.get('todos', [])
if not todos:
return div(p('暂无待办事项')).class_name('empty-list')
return div(
h2('待办事项'),
ul(
*[TodoItem(todo) for todo in todos]
)
).class_name('todo-list')
def TodoItem(props):
return li(
input().type('checkbox').checked(props.get('completed', False)),
span(props.get('text', '')).class_name(
'completed' if props.get('completed', False) else ''
)
).class_name('todo-item')组件库示例
下面是一个简单的组件库示例:
按钮组件
python
from pure.html import button
from pure.clx import clx
def Button(props):
# 提取 props
text = props.get('text', '')
variant = props.get('variant', 'primary')
size = props.get('size', 'medium')
disabled = props.get('disabled', False)
full_width = props.get('fullWidth', False)
# 构建类名
classes = clx(
'btn',
f'btn-{variant}',
f'btn-{size}',
{'btn-disabled': disabled, 'btn-full': full_width}
)
# 返回按钮元素
return button(text) \
.class_name(classes) \
.disabled(disabled) \
.type(props.get('type', 'button')) \
.onclick(props.get('onClick', ''))卡片组件
python
from pure.html import div, h2, p, img
def Card(props):
# 提取 props
title = props.get('title', '')
content = props.get('content', '')
image_url = props.get('image', '')
# 构建卡片内容
card_content = []
if image_url:
card_content.append(
img().src(image_url).alt(title).class_name('card-image')
)
card_content.extend([
h2(title).class_name('card-title'),
p(content).class_name('card-content')
])
# 添加操作按钮
actions = props.get('actions', [])
if actions:
action_buttons = div(
*[Button(action) for action in actions]
).class_name('card-actions')
card_content.append(action_buttons)
# 返回卡片元素
return div(
*card_content
).class_name('card')表单组件
python
from pure.html import form, div, label, input, textarea, button
def TextField(props):
id = props.get('id', '')
label_text = props.get('label', '')
return div(
label(label_text).for_(id) if label_text else None,
input() \
.type(props.get('type', 'text')) \
.id(id) \
.name(props.get('name', id)) \
.placeholder(props.get('placeholder', '')) \
.value(props.get('value', '')) \
.required(props.get('required', False))
).class_name('form-field')
def Form(props):
fields = props.get('fields', [])
submit_text = props.get('submitText', '提交')
return form(
*[TextField(field) for field in fields],
button(submit_text).type('submit').class_name('btn btn-primary')
) \
.action(props.get('action', '')) \
.method(props.get('method', 'post')) \
.class_name('form')最佳实践
1. 保持组件简单
每个组件应该只做一件事,并且做好。如果一个组件变得复杂,考虑将其拆分成更小的组件。
2. 使用有意义的命名
为组件和 props 使用描述性的名称:
python
# 不好的命名
def C(p):
return div(p.get('t'))
# 好的命名
def Card(props):
return div(props.get('title'))3. 提供默认值
为 props 提供合理的默认值,使组件更易用:
python
def Button(props):
# 提供默认值
text = props.get('text', '按钮')
variant = props.get('variant', 'primary')
return button(text).class_name(f'btn btn-{variant}')4. 文档化组件
为组件添加文档字符串,说明其用途和参数:
python
def Alert(props):
"""
显示警告消息的组件
参数:
type (str): 警告类型,可选值: 'info', 'success', 'warning', 'error'
message (str): 显示的消息
dismissible (bool): 是否可关闭
"""
alert_type = props.get('type', 'info')
message = props.get('message', '')
dismissible = props.get('dismissible', False)
return div(
message,
button('×').class_name('close-btn') if dismissible else None
).class_name(f'alert alert-{alert_type}')组件测试
测试组件以确保其正确工作:
python
def test_button_component():
# 测试默认按钮
default_button = Button({})
assert 'btn' in default_button.get_attr('class')
assert 'btn-primary' in default_button.get_attr('class')
# 测试禁用按钮
disabled_button = Button({'disabled': True})
assert disabled_button.get_attr('disabled') == 'disabled'
# 测试自定义按钮
custom_button = Button({
'text': '点击我',
'variant': 'danger',
'size': 'large'
})
assert custom_button.get_children()[0] == '点击我'
assert 'btn-danger' in custom_button.get_attr('class')
assert 'btn-large' in custom_button.get_attr('class')下一步
现在你已经了解了如何创建和使用组件,可以继续学习:
- 属性 - 深入了解属性系统
- TailwindCSS 集成 - 学习如何为组件添加样式
- API 参考 - 查看完整的 API 文档