- ConfigProvider 全局配置 Next 组件
- 安装方法
- 开发指南
- 何时使用
- 基本使用
- ConfigProvider
- ConfigProvider.config(Component)
- ConfigProvider.getContextProps(props, displayName)
- ConfigProvider.getContext()
- ConfigProvider.initLocales(locales)
- ConfigProvider.setLanguage(language)
- ConfigProvider.setLocale(locale)
- ConfigProvider.setDirection(dir)
- ConfigProvider.getLocale()
- ConfigProvider.getLanguage()
- ConfigProvider.getDirection()
- 使用注意
- 代码示例
- 相关区块
ConfigProvider 全局配置 Next 组件
如果项目中使用的是 0.x 版本的基础组件(@icedesign/base, @ali/ice, @alife/next),请在左侧导航顶部切换组件版本。
安装方法
- 在命令行中执行以下命令
npm install @alifd/next@latest -S
开发指南
何时使用
修改组件类名前缀,Next 组件类名的默认前缀都是 'next-',如 'next-btn',你可能在以下两种情况下想改变这个默认前缀:
自定义组件品牌,如 'my-btn','my-select'
一个页面中同时引入两个主题,防止相同类名样式互相覆盖
实现多语言文案切换
开启 Pure Render 模式,提高性能,注意同时可能会带来副作用
基本使用
指定多语言文案
通过 <ConfigProvider locale={localeObj}>
传入语言包,以支持多语言。目前 Fusion 内置的 locale 库支持中英繁日四种语言,覆盖各组件的简单词汇,例如:确定、取消、展开、收起、下一页等, 简单词汇映射表可参考 https://unpkg.com/@alifd/next/lib/locale/(ConfigProvider 提供简单组件简单词汇国际化能力,由于日期时间的国际化较为特殊,例如中国的日历是从周一到周日,美国的日历是从周日到周六等,时间相关的组件如DatePicker等需要国际化,请查看相应组件文档。)
可通过两种方式设置多语言文案,两种方式接收的对象格式略有不同:
- 1.设置组件自身 locale 属性
{
key1: value1,
key2: value2
}
- 2.ConfigProvider 的 locale 属性 (推荐)
{
component1: {
key1: value1,
key2: value2
},
component2: {
key1: value1,
key2: value2
}
}
优先级顺序为: 组件自身 locale > 最近 ConfigProvider 的 locale > 更远父级 ConfigProvider 的 locale
(注: 由于Dialog.show()
Message.show()
等函数式方法的特殊性,他们的将默认读取页面上的root context。当页面上有多个包含<ConfigProvider/>
的 ReactDOM.render()
方法调用时,由第一个渲染的决定root context)
import { ConfigProvider, DatePicker } from '@alifd/next';
const localeDatePicker = {
placeholder: 'localeDatePicker placeholder'
};
const localeGlobal = {
DatePicker: {
placeholder: 'localeGlobal placeholder'
}
};
class App extends React.Component {
render() {
return (
<div>
<ConfigProvider locale={localeGlobal}>
<DatePicker /> should be 'localeGlobal placeholder'
</ConfigProvider>
<br />
<br />
<ConfigProvider locale={localeGlobal}>
<DatePicker locale={localeDatePicker} /> should be 'localeDatePicker placeholder'
</ConfigProvider>
</div>
);
}
}
根据引入组件库方式的不同(CDN直接引用、作为依赖引用),使用语言包的方式略有差异,具体见如下代码:
import { ConfigProvider, DatePicker } from '@alifd/next';
import enUS from '@alifd/next/lib/locale/en-us';
// import zhCN from '@alifd/next/lib/locale/zh-cn';
// import zhTW from '@alifd/next/lib/locale/zh-tw';
// import jaJP from '@alifd/next/lib/locale/ja-jp';
// 如果应用中直接引入的是 cdn 上的 next-with-locales.js 文件
// 需要按照下面的方式引入国际化文案文件
// const { ConfigProvider, DatePicker, locales } = window.Next;
// const enUS = locales['en-us'];
class App extends React.Component {
render() {
return (
<ConfigProvider locale={enUS}>
<DatePicker />
</ConfigProvider>
);
}
}
如果内置的 locale 库不满足你的需求(比如想支持法语、德语、西班牙语),你也可以参考 https://unpkg.com/@alifd/next/lib/locale/ 来自定义语言包,按照如下格式传入给 locale 即可:
{
DatePicker: {
datePlaceholder: 'Select date',
monthPlaceholder: 'Select month',
yearPlaceholder: 'Select year',
rangeStartPlaceholder: 'Start date',
rangeEndPlaceholder: 'End date',
ok: 'OK',
clear: 'Clear'
},
Dialog: {
// ...
},
// ...
}
修改组件类名前缀
- 为你的应用包裹 ConfigProvider,并设置相应的 prefix
entry.jsx
class App extends React.Component {
render() {
return (
<ConfigProvider prefix="my-">
<div>
<Input />
<Button>Submit</Button>
</div>
</ConfigProvider>
);
}
}
- scss 入口文件中在引入主题 scss 文件前,设置相应的
$css-prefix
entry.scss
$css-prefix: "my-";
@import "~@alifd/theme-xxx/index.scss";
开启 Pure Render
import { ConfigProvider, DatePicker } from '@alifd/next';
class App extends React.Component {
render() {
return (
<ConfigProvider pure>
<DatePicker />
</ConfigProvider>
);
}
}
如何让组件支持 ConfigProvider ?
import { ConfigProvider } from '@alifd/next';
import locale from './locale';
const { config } = ConfigProvider;
class Component extends React.Component {
static propTypes = {
prefix: PropTypes.string,
locale: PropTypes.object,
pure: PropTypes.bool
};
static defaultProps = {
prefix: 'next-',
locale: locale,
pure: false
};
render() {
const { prefix, locale, pure } = this.props;
// ...
}
}
export default config(Component);
API
ConfigProvider
参数 | 说明 | 类型 | 默认值 |
---|---|---|---|
errorBoundary | 是否开启错误捕捉 errorBoundary如需自定义参数,请传入对象 对象接受参数列表如下:fallbackUI Function(error?: {}, errorInfo?: {}) => Element 捕获错误后的展示afterCatch Function(error?: {}, errorInfo?: {}) 捕获错误后的行为, 比如埋点上传 | Boolean/Object | false |
pure | 是否开启 Pure Render 模式,会提高性能,但是也会带来副作用 | Boolean | - |
warning | 是否在开发模式下显示组件属性被废弃的 warning 提示 | Boolean | true |
rtl | 是否开启 rtl 模式 | Boolean | - |
children | 组件树 | ReactElement | - |
ConfigProvider.config(Component)
传入组件,生成受 ConfigProvider 控制的 HOC 组件,如果组件没有声明 shouldComponentUpdate 方法,会添加如下 shouldComponentUpdate 方法以支持 ConfigProvider 的 pure 属性
Component.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
if (this.props.pure) {
return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState);
}
return true;
};
ConfigProvider.getContextProps(props, displayName)
传入组件的 props 和 displayName,得到和 childContext 计算过的包含有 preifx/locale/pure 的对象,一般用于通过静态方法生成脱离组件树的组件。
ConfigProvider.getContext()
通过该方法可以获取到 ConfigProvider 的上下文,格式如下。若有多层级 ConfigProvider 嵌套,会返回merge后的结果,关系近的优先。
{
prefix: nextPrefix,
locale: nextLocale,
pure: nextPure,
warning: nextWarning
}
ConfigProvider.initLocales(locales)
配置所有语言包, 可配合 ConfigProvider.setLanguage
方法,确定组件使用的语言包。
ConfigProvider.initLocales({
'zh-cn': {},
'en-us': {}
});
ConfigProvider.setLanguage(language)
设置语言,参数 language
需要能在 ConfigProvider.initLocales
方法传入的参数的 key 中找到, 默认为 zh-cn
ConfigProvider.setLanguage('zh-cn');
ConfigProvider.setLocale(locale)
直接设置语言包
// 相当于 同时用ConfigProvider.initLocales 和 ConfigProvider.setLanguage
ConfigProvider.setLocale({
DatePicker: {},
Dialog: {}
});
ConfigProvider.setDirection(dir)
设置组件展示方向,当传入 rtl
时,会在组件的根DOM节点加上 dir="rtl"
,同时组件展示rtl视觉。可用于阿拉伯等阅读顺序从右到左的国家。
ConfigProvider.setDirection('rtl');
ConfigProvider.getLocale()
获取当前的语言包
ConfigProvider.getLanguage()
获取当前设定的语言
ConfigProvider.getDirection()
获取当前设定的方向<!— api-extra-end —>
使用注意
减小应用中 webpack 打包 moment 体积
Next 1.x 中将 moment 作为自己的 peerDependencies 而非 dependencies,所以用户需要自己在应用中引入 moment 的 cdn 文件 moment-with-locales.js 或者本地安装 moment 打包进自己的应用。对于后者,由于 moment 在引入 locale 文件时存在这样的代码:require('./locale/' + name)
,如果用 webpack 构建,会打包进所有的 locale 文件,增加构建后文件的体积,目前社区比较主流的解决方案有以下两种:
const webpack = require('webpack');
module.exports = {
// ...
plugins: [
// 打包指定需要的语言文件
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn|ja/)
// 只打包有过引用的语言文件,应用中需要添加如:`import 'moment/locale/zh-cn';`
// new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
]
};
为自定义组件添加 displayName
ConfigProvider 获取组件对应的多语言文案,是通过组件的 displayName 或者 name 获取的,但是压缩混淆的过程中有可能会修改函数的 name,因此如果想支持在 ConfigProvider 下实现切换多语言切换,请为组件如下手动添加 displayName:
class CustomComponent extends React.Component {
static displayName = 'CustomComponent';
// ...
}
或者使用 babel-plugin-transform-react-es6-displayname
自动在编译期间添加 displayname。
获取 HOC 组件内部组件的引用
由于 HOC 本身的限制,我们不能直接像下面代码那样获取内部组件的引用,从而调用它的一些内部方法:
class App extends React.Component {
componentDidMount() {
// 报错
this.refs.hoc.someMethod();
}
render() {
return <HOC ref="hoc" />;
}
}
为了解决这个问题,我们为调用 config 方法生成的 HOC 组件添加了 getInstance 方法,你可以如下调用:
class App extends React.Component {
componentDidMount() {
this.refs.hoc.getInstance().someMethod();
}
render() {
return <HOC ref="hoc" />;
}
}
代码示例
展示如何配合 ConfigProvider 实现具有国际化能力的组件。
查看源码在线预览
import { ConfigProvider, Button, Select } from '@alifd/next';
import PropTypes from 'prop-types';
const { config, getContextProps } = ConfigProvider;
const { Option } = Select;
const locales = {
'zh-cn': {
ClickMe: {
clickMe: '点我!'
},
Toast: {
close: '关闭'
}
},
'en-us': {
ClickMe: {
clickMe: 'click me!'
},
Toast: {
close: 'close'
}
}
};
class ClickMe extends React.Component {
static propTypes = {
locale: PropTypes.object,
onClick: PropTypes.func
};
static defaultProps = {
locale: locales['zh-cn'].ClickMe,
onClick: () => {}
};
render() {
const { locale, onClick } = this.props;
return (
<Button onClick={onClick}>{locale.clickMe}</Button>
);
}
}
class Toast extends React.Component {
static propTypes = {
locale: PropTypes.object,
afterClose: PropTypes.func
};
static defaultProps = {
locale: locales['zh-cn'].Toast,
afterClose: () => {}
};
constructor(props) {
super(props);
this.state = {
visible: false
};
this.handleClose = this.handleClose.bind(this);
}
handleClose() {
this.setState({
visible: false
});
this.props.afterClose();
}
render() {
return (
<div className="toast">
<Button type="primary" onClick={this.handleClose}>
{this.props.locale.close}
</Button>
</div>
);
}
}
Toast.create = (props = {}) => {
const mountNode = document.createElement('div');
document.body.appendChild(mountNode);
const closeChain = () => {
ReactDOM.unmountComponentAtNode(mountNode);
document.body.removeChild(mountNode);
};
const newLocale = getContextProps(props, 'Toast').locale;
ReactDOM.render(<Toast afterClose={closeChain} locale={newLocale} />, mountNode);
};
const NewClickMe = config(ClickMe);
const NewToast = config(Toast);
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
language: 'zh-cn'
};
this.handleClick = this.handleClick.bind(this);
this.handleChangeLanguage = this.handleChangeLanguage.bind(this);
}
handleClick() {
NewToast.create();
}
handleChangeLanguage(language) {
this.setState({
language
});
}
render() {
const { language } = this.state;
return (
<ConfigProvider locale={locales[language]}>
<div>
<div className="select-language">
<Select value={language} onChange={this.handleChangeLanguage}>
<Option value="zh-cn">zh-cn</Option>
<Option value="en-us">en-us</Option>
</Select>
</div>
<NewClickMe onClick={this.handleClick} />
</div>
</ConfigProvider>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.toast {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
width: 200px;
height: 100px;
line-height: 100px;
text-align: center;
background: white;
box-shadow: 3px 3px 5px 0 rgba(0, 0, 0, .32);
}
.toast .next-btn {
margin: auto;
}
.select-language {
margin-bottom: 20px;
}
最简单的用法,展示 ConfigProvider 是如何工作的。
查看源码在线预览
import { ConfigProvider } from '@alifd/next';
import PropTypes from 'prop-types';
const { config } = ConfigProvider;
class Output extends React.Component {
static propTypes = {
prefix: PropTypes.string,
locale: PropTypes.object,
pure: PropTypes.bool
};
static defaultProps = {
prefix: 'next-',
locale: {
hello: '你好'
},
pure: false
};
render() {
const { prefix, locale, pure } = this.props;
return (
<ul>
<li>prefix: {prefix}</li>
<li>locale: {JSON.stringify(locale)}</li>
<li>pure: {pure.toString()}</li>
</ul>
);
}
}
const NewOutput = config(Output);
class Demo extends React.Component {
render() {
return (
<ConfigProvider prefix="custom-" locale={{ Output: { hello: 'hello' } }} pure>
<NewOutput />
</ConfigProvider>
);
}
}
ReactDOM.render(<Demo />, mountNode);
展示目前 Next 组件中支持国际化的组件。
查看源码在线预览
import { ConfigProvider, Button, Radio, Calendar, Card, DatePicker, Dialog, Pagination, TimePicker, Timeline, Transfer, Select, Upload, Table } from '@alifd/next';
import enUS from '@alifd/next/lib/locale/en-us';
import zhCN from '@alifd/next/lib/locale/zh-cn';
// If the application directly imports the next-with-locales.js file from cdn
// it need to import locale file in the following way
// import { locales } from '@alifd/next';
// const enUS = locales['en-us'];
// const zhCN = locales['zh-cn'];
const RangePicker = DatePicker.RangePicker;
const transferDataSource = (() => {
const dataSource = [];
for (let i = 0; i < 10; i++) {
dataSource.push({
label: `content ${i}`,
value: `${i}`,
disabled: i % 4 === 0
});
}
return dataSource;
})();
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
lang: 'en-us'
};
this.changeLang = this.changeLang.bind(this);
this.showDialog = this.showDialog.bind(this);
}
changeLang(lang) {
this.setState({
lang
});
}
showDialog() {
Dialog.confirm({
title: 'Confirm',
content: 'Are you sure you want to delete all alert e-mails waiting in queue?'
});
}
render() {
const locale = this.state.lang === 'en-us' ? enUS : zhCN;
return (
<div>
<div className="change-locale">
<span style={{ marginRight: 16 }}>Change locale of components: </span>
<Radio.Group shape="button" size="large" defaultValue="en-us" onChange={this.changeLang}>
<Radio key="en" value="en-us">English</Radio>
<Radio key="cn" value="zh-cn">中文</Radio>
</Radio.Group>
</div>
<ConfigProvider locale={locale}>
<div className="locale-components">
<Button type="primary" onClick={this.showDialog}>Show Dialog</Button>
<Select style={{ width: '150px' }} dataSource={['hello', 'bye']} />
<DatePicker />
<TimePicker />
<RangePicker />
<Calendar style={{ width: '350px', padding: '12px', border: '1px solid #C4C6CF', borderRadius: '3px' }} shape="card" />
<Pagination defaultCurrent={2} />
<Transfer dataSource={transferDataSource} defaultValue={['3']} defaultLeftChecked={['1']} titles={['Source', 'Target']} />
<Table style={{ width: '500px' }} dataSource={[]}>
<Table.Column title="Name" dataIndex="name" filters={[{ label: 'Filter 1', value: '1' }, { label: 'Filter 2', value: '2' }]} />
<Table.Column title="Age" dataIndex="age" />
</Table>
<Card style={{ width: '300px' }} title="Title">
<div style={{ height: '250px', background: '#F7F8FA' }}></div>
</Card>
<Timeline fold={[{foldArea: [1, 2], foldShow: true}]}>
<Timeline.Item title="Signed" content="Signed, sign Alibaba is a small post office, thanks to the use of STO, look forward to once again at your service" time={'2016-06-10 10:30:00'} state="process"/>
<Timeline.Item title="Ship" content="Express has arrived in Hangzhou, Zhejiang Binjiang company" time={'2016-06-10 09:30:00'} />
<Timeline.Item title="Ship" content="Zhejiang Hangzhou Riverside company sent a member for you to send pieces" time={'2016-06-10 09:03:00'} />
<Timeline.Item title="Ship" content="Zhejiang Hangzhou Transshipment Center has been issued" time={'2016-06-10 06:10:00'} />
</Timeline>
<Upload.Dragger style={{ width: '500px' }}
listType="image"
action="https://www.easy-mock.com/mock/5b713974309d0d7d107a74a3/alifd/upload"
accept="image/png, image/jpg, image/jpeg, image/gif, image/bmp" />
</div>
</ConfigProvider>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.change-locale {
border-bottom: 1px solid #d9d9d9;
padding-bottom: 16px;
}
.locale-components > * {
margin: 16px 0;
display: block;
}
使用 <Consumer>
可以方便地读取 <ConfigProvider>
中上下文的数据
查看源码在线预览
import { ConfigProvider } from '@alifd/next';
import PropTypes from 'prop-types';
const localeSettings = {
momentLocale: 'fr-FR',
CustomizedComponent: {
helloWorld: 'hello, world'
}
};
const App = ({ children }) => (
<ConfigProvider
prefix="customized-"
locale={localeSettings}
pure
warning={false}
>
{children}
</ConfigProvider>
);
App.propTypes = {
children: PropTypes.node
};
const Child = () => (
<ConfigProvider.Consumer>
{
context => (
<div className="context-data">
<h3>Context's state</h3>
<pre>{JSON.stringify(context, false, 2)}</pre>
</div>
)
}
</ConfigProvider.Consumer>
);
const Demo = () => (
<App>
<Child />
</App>
);
ReactDOM.render(<Demo />, mountNode);
.context-data {
padding: 0 32px 32px;
border: 3px dashed #aaa;
border-radius: 9px;
}
组件RTL样式展示(目前部分支持)
查看源码在线预览
import { ConfigProvider, Button, Radio, Menu, Calendar, DatePicker, Dialog, TimePicker, Timeline, Select } from '@alifd/next';
const { SubMenu, Item, Group, Divider } = Menu;
const RangePicker = DatePicker.RangePicker;
// Set global direction to 'rtl'. This affects the whole page
// ConfigProvider.setDirection('rtl');
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
dir: 'rtl'
};
this.changeDir = this.changeDir.bind(this);
this.showDialog = this.showDialog.bind(this);
}
changeDir(value) {
this.setState({
dir: value
});
}
showDialog() {
Dialog.confirm({
title: 'Confirm',
content: 'Are you sure you want to delete all alert e-mails waiting in queue?'
});
}
render() {
return (
<div>
<div className="change-rtl">
<span style={{ marginRight: 16 }}>Change direction of components: </span>
<Radio.Group shape="button" size="large" value={this.state.dir} onChange={this.changeDir}>
<Radio key="rtl" value="rtl">RTL</Radio>
<Radio key="ltr" value="ltr">LTR</Radio>
</Radio.Group>
</div>
<br />
<hr />
<ConfigProvider rtl={this.state.dir === 'rtl'}>
<div className="locale-components" dir={this.state.dir}>
<Button type="primary" onClick={this.showDialog}>Show Dialog</Button>
<Select style={{ width: '150px' }} dataSource={['hello', 'bye']} />
<RangePicker showTime/>
<Calendar style={{ width: '350px', padding: '12px', border: '1px solid #C4C6CF', borderRadius: '3px' }} shape="card" />
<Timeline fold={[{foldArea: [1, 2], foldShow: true}]}>
<Timeline.Item title="Signed" content="Signed, sign Alibaba is a small post office, thanks to the use of STO, look forward to once again at your service" time={'2016-06-10 10:30:00'} state="process"/>
<Timeline.Item title="Ship" content="Express has arrived in Hangzhou, Zhejiang Binjiang company" time={'2016-06-10 09:30:00'} />
<Timeline.Item title="Ship" content="Zhejiang Hangzhou Riverside company sent a member for you to send pieces" time={'2016-06-10 09:03:00'} />
<Timeline.Item title="Ship" content="Zhejiang Hangzhou Transshipment Center has been issued" time={'2016-06-10 06:10:00'} />
</Timeline>
<Menu className="my-menu" defaultOpenKeys="sub-menu">
<Item key="1">Option 1</Item>
<Item disabled key="2">Disabled option 2</Item>
<Divider key="divider" />
<Group label="Group">
<Item key="group-1">Group option 1</Item>
<Item key="group-2">Group option 2</Item>
</Group>
<Divider />
<SubMenu key="sub-menu" label="Sub menu">
<Item key="sub-1">Sub option 1</Item>
<Item key="sub-2">Sub option 2</Item>
<Item disabled key="sub-3">
<a href="https://www.taobao.com/" target="__blank">Disabled Option Link 3</a>
</Item>
<Item key="sub-4">
<a href="https://www.taobao.com/" target="__blank">Option Link 4</a>
</Item>
</SubMenu>
<Item key="3" helper="CTRL+P">Option 3</Item>
<Item disabled key="4">
<a href="https://www.taobao.com/" target="__blank">Disabled Option Link</a>
</Item>
<Item key="5">
<a href="https://www.taobao.com/" target="__blank">Option Link</a>
</Item>
</Menu>
</div>
</ConfigProvider>
</div>
);
}
}
ReactDOM.render(<Demo />, mountNode);
.my-menu {
width: 200px;
}
.change-locale {
border-bottom: 1px solid #d9d9d9;
padding-bottom: 16px;
}
.locale-components > * {
margin: 16px 0;
display: block;
}
使用 <ErrorBoundary>
可以避免由于局部区域的错误,所引起的页面白屏。
查看源码在线预览
import { ConfigProvider, Button } from '@alifd/next';
const { ErrorBoundary, config } = ConfigProvider;
class Demo extends React.Component {
render() {
if (this.props.throwError) {
throw Error('There is something going wrong!');
} else {
return (
<span>normal</span>
);
}
}
}
const NewDemo = config(Demo);
const fallbackUI = (props) => {
const { error, errorInfo } = props;
return <span style={{color: 'red'}}>{error.toString()}</span>;
};
class App extends React.Component {
state = {
throwError: false
};
onClick = () => {
this.setState({
throwError: true
});
};
render() {
return (<div>
Click to throw an error <Button type="primary" onClick={this.onClick}>trigger error</Button>
<br/>
<br/>
Default fallback UI:
<hr />
<ConfigProvider errorBoundary>
<NewDemo throwError={this.state.throwError}/>
</ConfigProvider>
<br/>
<br/>
Customize fallback UI of configed Component(Basic Components / Biz Components):
<hr />
<ConfigProvider errorBoundary={{
fallbackUI: props => {
const { error, errorInfo } = props;
return <span style={{color: 'red'}}>Error: {error.toString()}</span>;
},
afterCatch: () => {
console.log('catching');
}
}}>
<NewDemo throwError={this.state.throwError}/>
</ConfigProvider>
</div>);
}
}
ReactDOM.render(<App />, mountNode);