@fluentui/相接风格
这个 merge-styles 图书馆提供通过javascript装入样式的公用事业。 其设计是为了通过javascript来简化组件的样式. 它生成css类,而不是使用内线型来保证我们能使用cs特征,如伪选择器(:hover)和亲子选择器(媒体查询).
库为速度和大小所建;整个包为2.62k gziped. 它没有依赖性,除了 tslib。 。 。 .
简单用途 :
import { mergeStyles, mergeStyleSets } from '@fluentui/merge-styles';
// Produces 'css-0' class name which can be used anywhere
mergeStyles({ background: 'red' });
// Produces a class map for a bunch of rules all at once
mergeStyleSets({
root: { background: 'red' },
child: { background: 'green' },
});
// Returns { root: 'root-0', child: 'child-1' }
两个工具行为都类似于深对象. sign; 您可以将许多对象倒塌成一个类名或类图 .
基本的想法是提供工具,这些工具可以在一个或多个 Css 样式对象中取取代表给定元素的样式,并返回单个类名. 如果同一套型号相接相接,则同名回回,再无再注册.
动机
确定运行时间的规则比传统建设时间静态生成的css有一些好处:
-
只有需要的注册类,当需要时,减少总的选取者数并改进TTG.
-
根据背景主题要求动态地创建出新的类布局. (在DIV内部使用不同的主题而不下载多份cs规则定义
-
使用 JavaScript 来定义类内容( 使用像色彩转换器这样的工具, 或者重用常数成为可能)
-
允许控制库将定制的造型与规则合并,避免像css选择器特异性这样的复杂.
-
简化RTL处理;左起而成为RTL中的权利,在实际规则中. 没有复杂
html[dir=rtl]前缀需要,可以缓解出乎意料的特异性错误。 (你可以使用/* noflip */注释,以避免必要时翻转。 ) -
减少捆绑大小。 自动处理供应商前缀,单元提供,RTL翻转,和比值/铺设扩展(例如比值会自动向外扩展到比值TRBL,这样在将事物合并时我们就可以避免特殊问题. )
-
减少运行于 CSS 预处理器的构建时间管理费 .
-
TypeScript 类型安全; 拼写"背景"错误并获得构建间歇.
什么是权衡? 使用 JavaScript 处理样式是否有缺点 ?
在静态解决方案中,很少需要运行时间评价;一切都是按原样注入的. 诸如自动前缀等事物和像相思混音器等语言特定处理,都在构建时得到评价.
在运行时的造型中, 浏览器会评估其中的大部分, 所以你要为此付出代价。 然而,通过像回忆式那样的性能优化,你可以将这一点大大地降低到最低限度,你得到了上面列举的所有强健性.
API 密码
Api表面由3个方法和少数接口组成:
mergeStyles(..args[]: IStyle[]): string - 采用一个或多个样式对象,按正确的顺序合并,并产生一个单一的 Css 类名称,可以注入任何组件.
mergeStyleSets(...args[]: IStyleSet[]): { [key: string]: string } - 采用一个或多个样式设定的对象,每个对象由一组区域组成,每个区域将产生一个类名。 使用此功能类似于调用对象中每个属性的合并Styles,但当多个样式集被合并时,确保我们维持设定顺序.
concatStyleSets(...args[]: IStyleSet[]): IStyleSet - 在某些情况下,只需要组合样式集,而不实际生成类名(生成类名是性能成本). 这个工具返回一个将许多集合并在一起的单个集.
concatStyleSetsWithProps(props: {}, ...args[]: IStyleSet[]): IStyleSet - 类似 concatStyleSet 但是,包含样式功能评价的样式集在调制前会进行评估.
示例
const result = concatStyleSetsWithProps<IFooProps, IFooStyles>(
{ foo: 'bar' },
(props: IFooProps) => ({ root: { background: props.foo } }),
(props: IFooProps) => ({ root: { color: props.foo } }),
);
词汇
A 类 样式对象 代表 Css 规则的集合,但名称是骆驼Cased 而不是kebab- cased. 示例
let style = {
backgroundColor: 'red',
left: 42,
};
此外, 样式对象 能够包含选择器 :
let style = {
backgroundColor: 'red',
':hover': {
backgroundColor: 'blue';
},
'.parent &': { /* parent selector */ },
'& .child': { /* child selector */ }
};
A 类 样式集 代表区域映射到样式对象。 在构建一个组件时,需要为每个需要造型的元素生成类名. 你将在一个 样式集。 。 。 .
let styleSet = {
root: { background: 'red' },
button: { margin: 42 },
};
基本用途
在构建组件时,您需要 样式集 向元素的类属性注入类名的图.
建议的模式是在单独的函数中提供类名,一般在单独的文件中提供 ComponentName.classNames.ts。 。 。 .
import { IStyle, mergeStyleSets } from '@fluentui/merge-styles';
export interface IComponentClassNames {
root: string;
button: string;
buttonIcon: string;
}
export const getClassNames = (): IComponentClassNames => {
return mergeStyleSets({
root: {
background: 'red',
},
button: {
backgroundColor: 'green',
},
buttonIcon: {
margin: 10,
},
});
};
然后,类图可用于组件:
import { getClassNames } from './MyComponent.classNames';
export const MyComponent = () => {
let { root, button, buttonIcon } = getClassNames();
return (
<div className={root}>
<button className={button}>
<i className={buttonIcon} />
</button>
</div>
);
};
选择器
基本伪选取者(:hover,:活性等)
自定义选择器可以在内部定义 IStyle 定义:
{
background: 'red',
':hover': {
background: 'green'
}
}
默认情况下,该规则会被附加到当前选择者范围. 也就是说,在上述情况下,在使用时将插入两项规则 mergeStyles数字 :
.css-0 {
background: red;
}
.css-0:hover {
background: green;
}
父母/子女选择者
在某些情况下,您可能需要使用父母或子女选择器。 为此,您可以从零开始定义选择器,并使用 & 字符来代表生成的类名。 使用时 &,则忽略了当前范围。 示例
{
// selector relative to parent
'.ms-Fabric--isFocusVisible &': {
background: 'red'
}
// selector for child
'& .child' {
background: 'green'
}
}
这将记录规则:
.ms-Fabric--isFocusVisible .css-0 {
background: red;
}
.css-0 .child {
background: green;
}
全局选择器
虽然我们建议避免全球选择者,但有些案例是有意义的,可以在全球登记。 记住,全球选择器无法保证独有性,并且可能会遇到特殊问题和版本问题,如果您的库有两个不同的版本在页面上被渲染.
要在全球注册一个选择器,请将其包入 :global() 包装 :
{
':global(button)': {
overflow: 'visible'
}
}
媒体和专题查询
媒体查询可以通过选择器应用. 例如,这种样式将产生一个在600px以上时具有红色背景的类,在600px以上或以下时具有绿色背景的类:
mergeStyles({
background: 'red',
'@media(max-width: 600px)': {
background: 'green',
},
'@supports(display: grid)': {
display: 'grid',
},
});
生产:
.css-0 {
background: red;
}
@media (max-width: 600px) {
.css-0 {
background: green;
}
}
@supports (display: grid) {
.css-0 {
display: grid;
}
}
引用合并范围内的儿童元素StyleSets
一个重要的概念 mergeStyleSets 它为给定的元素绘制一个类名图:
mergeStyleSets({
root: { background: 'red' }
thumb: { background: 'green' }
});
生产:
.root-0 {
background: red;
}
.thumb-1 {
background: green;
}
在某些情况下,您可能需要与父母互动来改变儿童区域。 例如,当父母被徘徊时,改变孩子的背景. 我们建议使用全球不变的静态类名 以母元素为目标:
const classNames = {
root: 'Foo-root',
child: 'Foo-child',
};
mergeStyleSets({
root: [classNames.root, { background: 'lightgreen' }],
child: [
classNames.child,
{
[`.${classNames.root}:hover &`]: {
background: 'green',
},
},
],
});
这里的重要部分是选择者没有任何可变的信息. 在上述例子中,
若为 classNames.root 它要求规则在变异时重新登记
这将是一个表演命中.
自定义类名
默认使用时 mergeStyles,生成的类名将使用前缀 css- 之后是数字,必要时创造了独特的规则。 例如,生产的一等名会为"css-0".
使用时 mergeStyleSets,类名称自动使用区域名称作为前缀.
合并规则如下:
mergeStyleSets({ a: { ... }, b: { ... } })
将生成类名图 :
{ a: 'a-0', b: 'b-1' }
如果您想在任意一种情况下推翻默认前缀,您可以在 displayName 解决以下问题:
{
displayName: 'MyComponent',
background: 'red'
}
此生成 :
.MyComponent-0 {
background: red;
}
管理条件和州
样式对象可以用一个简单的对象来表示,但也可以是对象的数组. 合并函数将按给定顺序处理数组并合并事物. 他们也会忽略虚假的价值观,允许您以条件化结果.
在下面的例子中,生成的根类会因不同而不同 isToggled 状态 :
export const getClassNames = (isToggled: boolean): IComponentClassNames => {
return mergeStyleSets({
root: [
{
background: 'red',
},
isToggled && {
background: 'green',
},
],
});
};
RTL 支持
默认情况下, 当 dir="trtl" 旗出现时, 几乎所有主要的 rtl 敏感 CSS 属性都会自动翻转 HTML 页面标记.
有一些罕见的情景(线性梯度等)没有被翻转,以将捆绑大小保持在最小. 如果有丢失的边缘案件,请提交公关申请.
在难得的条件下,如果想要避免自动翻转,可以使用 @noflip 指令 :
mergeStyles({
left: '42px @noflip',
});
优化绩效
解决每个场景上的班级名称可能是一种不必要的开支,特别是在经常发生事情的热点地区。 为了优化,我们建议两项准则:
-
给你的
getClassNames函数,将所有输入参数平整为简单的不可变值。 这帮助memoizeFunction用于根据输入缓存结果. -
使用
memoizeFunction函数从@fluentui/utilities软件包以缓存结果,因为输入组合独特。 示例
import { memoizeFunction } from '@fluentui/utilities';
export const getClassNames = memoizeFunction((isToggled: boolean) => {
return mergeStyleSets({
// ...
});
});
注册字体
注册字体的正面示例 :
import { fontFace } from '@fluentui/merge-styles';
fontFace({
fontFamily: `"Segoe UI"`,
src: `url("//cdn.com/fontface.woff2) format(woff2)`,
fontWeight: 'normal',
});
注意,在诸如 fontFamily 如上所示,您可能需要在字符串中嵌入引号.
登记关键框架
注册动画键框示例 :
import { keyframes, mergeStyleSets } from '@fluentui/merge-styles';
let fadeIn = keyframes({
from: {
opacity: 0,
},
to: {
opacity: 1,
},
});
export const getClassNames = () => {
return mergeStyleSets({
root: {
animationName: fadeIn,
},
});
};
控制样式的注入
默认 merge-styles 将首先插入 style 元素作为第一个节点,然后附加和新建 style 元素作为前一个的下个兄弟添加.
在某些情况下,您可能想要控制样式的注入,以确保一些样式表比其他样式更具体。 要做到这一点,你可以添加一个占位符 style 标题中的元素 data-merge-styles 属性 :
<head>
<style data-merge-styles></style>
</head>
合并样式将确保在占位符后添加任何生成的样式.
服务器侧渲染
您可以导入 renderStatic 方法从 /lib/server 条目来渲染内容并提取本来会注册的 css 规则,作为字符串.
示例
import { renderStatic } from '@fluentui/merge-styles/lib/server';
let { html, css } = renderStatic(() => {
return ReactDOM.renderToString(...);
});
用于服务器侧渲染的洞:
- 在代码的文件范围中注册的规则不会被重新评价,因此不会包含在结果中. 尽量避免使用运行时未评估的类.
例如:
const rootClass = mergeStyles({ background: 'red' });
const App = () => <div className={rootClass} />;
// App will render, but "rootClass" is a string which won't get re-evaluated in this call.
renderStatic(() => ReactDOM.renderToString(<App/>);
-
使用
memoizeFunction环绕规则计算可以帮助过度的规则再计算性能间接费用. -
客户端的还水可能导致规则不匹配. 您可以在服务器侧应用一个命名空间,以确保不存在名称相撞.
未信任的样式数据
这个 css 返回时间 renderStatic (和 Stylesheet.getRules)是原始 CSS 文本,通常被写入 <style> 键。 A 类 <style> 元素是 HTML 原始文本,所以不区分大小写 </style 序列会终止它并注入标记.
为了防止这样, merge-styles 在 CSS 序列化边界中避开这些序列. 宣言价值也出现 < 财务报告和审定财务报表 > 作为 CSS 代码点出逃 \3C 财务报告和审定财务报表 \3E 。这些越狱是语义保留并解码回同字符,而不改变选择器、组合器、规则、自定义属性或键框语法。 同样, Stylesheet.serialize 发射 < 作为 \u003C 因此其输出可以嵌入内线 <script> 用于补液.
这种出逃是深入的防御,不能代替验证输入. 申请仍负责:
- 验证任何不信任的数据(用户或租户提供的品牌颜色,口音,背景图像 URL),然后将其作为样式值.
Stylesheet.insertRule,这是故意的 没有 因为它接受完整的规则 包括选择者 和选择者合法地包含<财务报告和审定财务报表>相邻字符,例如>儿童组合器。 永远不要向它传递不信任的数据.- 执行内容安全政策,限制任何注射的影响.
并注意,登记在 preserve - 这是什么 fontFace 财务报告和审定财务报表 keyframes 是否 - 由 Stylesheet.reset因此,他们每晚被再次接收 getRules(true) 调用同一进程。 不要从共享服务器进程中的每个请求不可信数据中获取字体面或键框 .
执行内容安全政策
一些内容安全政策防止无非ce的样式注入. 设置用于 merge-styles数字 :
Stylesheet.getInstance().setConfig({
cspSettings: { nonce: 'your nonce here' },
});
如果你在流利的UI React app内工作之前的办公室 UI 纤维反应),此设置也可以使用全局 window.FabricConfig.mergeStyles.cspSettings。请注意,必须在装入任何流体UI反应代码之前设定,否则可能无法正确应用.
window.FabricConfig = {
mergeStyles: {
cspSettings: { nonce: 'your nonce here' },
},
};
阴影DOM
merge-styles 有对 阴影 DOM。该特性可选择采用,并可逐步采用。 要启用您需要包含的特性 反应提供者数字 :
MergeStylesRootProvider: 充当您的应用的"全球"上下文. 你每页应该有一张.MergeStylesShadowRootProvider: 应用程序中每个阴影根的上下文。 你应该有一个这样的每个阴影根.
merge-styles 没有为在React中创建阴影根提供选项,因为你如何获得阴影根并不重要,只是你有一个参考. react-shadow 是一个库,可以在React中创建阴影根,并将被用在示例中.
阴影 DOM 示例
import { PrimaryButton } from '@fluentui/react';
import { MergeStylesRootProvider, MergeStylesShadowRootProvider } from '@fluentui/utilities';
import root from 'react-shadow';
const ShadowRoot = ({ children }) => {
// This is a ref but we're using state to manage it so we can force
// a re-render.
const [shadowRootEl, setShadowRootEl] = React.useState<HTMLElement | null>(null);
return (
<MergeStylesRootProvider>
<root.div className="shadow-root" delegatesFocus ref={setShadowRootEl}>
<MergeStylesShadowRootProvider shadowRoot={shadowRootEl?.shadowRoot}>{children}</MergeStylesShadowRootProvider>
</root.div>
</MergeStylesRootProvider>
);
};
<ShadowRoot>
<PrimaryButton>I'm in the shadow DOM!</PrimaryButton>
</ShadowRoot>
<PrimaryButton>I'm in the light DOM!</PrimaryButton>
用于更有效CSS的界定样式
你没有 需求 更新您的 merge-styles 用于支持阴影 DOM 的样式,但可以通过一些更新使样式更加有效.
阴影 DOM 支持实现于 merge-styles 通过使用 可构造样式表 并被“样式表密钥”瞄准. merge-styles 每个按键创建一个样式表,在Fluent中这意味着每个组件都有自己的样式表。 每个 MergeStylesShadowRootProvider 将只采用它包含的组件加全局工作表的样式(我们无法确定我们是否需要这个工作表,所以我们总是采用它)。 这意味着 MergeStylesShadowRootProvider 包含按钮的按钮将只采用按钮样式(加上全局样式),而不是复选框样式,使阴影根内的样式更加有效.
如果您使用 customizable 或者说 styled 用于这些函数的现有“范围”值是一个独特的密钥。 如果没有提供密钥 merge-styles 回到“全球”键。 这个全局密钥是一个包罗万象的,并允许我们支持在阴影DOM支持被添加之前所写的代码或者在React上下文之外被叫作代码.
全体 @fluentui/react 样式通过 customizable 财务报告和审定财务报表 styled (并在必要时对特定组件样式进行一些更新). 如果您的组件使用这些函数并设置“ 镜片” 属性, 您的组件将自动被设定范围 .
如果你在用的话 mergeStyles() (和其他 merge-styles APIs)直接,你的风格会被放入全球范围,并且仍然可以被放入阴影根中,只是没有尽可能优化.
样式范围界定示例
import { useMergeStylesHooks } from '@fluentui/react';
import { mergeStyles } from '@fluentui/merge-styles';
import type { ShadowConfig } from '@fluentui/merge-styles';
// This must be globally unique for the application
const MY_COMPONENT_STYLESHEET_KEY: string = 'my-unique-key';
const MyComponent = props => {
const { useWindow, useShadowConfig, useAdoptedStylesheet } = useMergeStylesHooks();
// Make sure multi-window scenarios work (e.g., pop outs)
const win: Window = useWindow();
const shadowConfig: ShadowConfig = useShadowConfig(MY_COMPONENT_STYLESHEET_KEY, win);
const styles = React.useMemo(() => {
// shadowConfig must be the first parameter when it is used
return mergeStyles(shadowConfig, myStyles);
}, [shadowConfig, myStyles]);
useAdoptedStylesheet(MY_COMPONENT_STYLESHEET_KEY);
};