ConfigProvider  全局配置 Next 组件

开发指南#

何时使用#

基本使用#

指定多语言文案#

通过 <ConfigProvider locale={localeObj}> 传入语言包,以支持多语言。目前 Fusion 内置的 locale 库支持中英繁日四种语言,覆盖各组件的简单词汇,例如:确定、取消、展开、收起、下一页等, 简单词汇映射表可参考 https://unpkg.com/@alifd/next/lib/locale/
(ConfigProvider 提供简单组件简单词汇国际化能力,由于日期时间的国际化较为特殊,例如中国的日历是从周一到周日,美国的日历是从周日到周六等,时间相关的组件如DatePicker等需要国际化,请查看相应组件文档。)

可通过两种方式设置多语言文案,两种方式接收的对象格式略有不同:

{
    key1: value1,
    key2: value2
}
{
    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: {
        // ...
    },
    // ...
}

修改组件类名前缀#

  1. 为你的应用包裹 ConfigProvider,并设置相应的 prefix

    entry.jsx

    class App extends React.Component {
        render() {
            return (
                <ConfigProvider prefix="my-">
                    <div>
                        <Input />
                        <Button>Submit</Button>
                    </div>
                </ConfigProvider>
            );
        }
    }
  2. 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#

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 实现具有国际化能力的组件。

code collapse
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);

最简单的用法,展示 ConfigProvider 是如何工作的。

code collapse
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;
}

展示目前 Next 组件中支持国际化的组件。

code collapse
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;
}

使用 <Consumer> 可以方便地读取 <ConfigProvider> 中上下文的数据

code collapse
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;
}

组件RTL样式展示(目前部分支持)

code collapse
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);

使用 <ErrorBoundary> 可以避免由于局部区域的错误,所引起的页面白屏。

code collapse

# 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()#

获取当前设定的方向

使用注意#

减小应用中 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" />;
    }
}