问题:react-intl用法部分
function Home(props) { return ( <DocumentTitle title={`Ant Design - ${props.intl.formatMessage({ id: 'app.home.slogan' })}`}> <div className="main-wrapper"> <Link /> <Banner {...props} /> <Page1 {...props} /> <Page2 {...props} /> <Page3 {...props} /> <Page4 {...props} /> <style dangerouslySetInnerHTML={{ __html: getStyle() }} /> </div> </DocumentTitle> ); } export default injectIntl(Home);//传入一个包裹组件WrappedComponent,返回一个InjectIntl组件实例
问题:源码分析部分
/* * Copyright 2015, Yahoo Inc. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ // Inspired by react-redux's `connect()` HOC factory function implementation: // https://github.com/rackt/react-redux import React, {Component} from 'react'; import invariant from 'invariant'; import {intlShape} from './types'; import {invariantIntlContext} from './utils'; function getDisplayName(Component) { return Component.displayName || Component.name || 'Component'; } export default function injectIntl(WrappedComponent, options = {}) { const { intlPropName = 'intl', withRef = false,//为WrappedComponent添加一个ref属性 } = options; class InjectIntl extends Component { static displayName = `InjectIntl(${getDisplayName(WrappedComponent)})`; static contextTypes = {//可以接受父组件存放在context中的intl属性 intl: intlShape, }; static WrappedComponent = WrappedComponent; constructor(props, context) { super(props, context); invariantIntlContext(context); } //getWrappedInstance调用时候欧返回我们的ref="wrappedInstance" getWrappedInstance() { invariant(withRef, '[React Intl] To access the wrapped instance, ' + 'the `{withRef: true}` option must be set when calling: ' + '`injectIntl()`' ); return this.refs.wrappedInstance; } render() { return ( <WrappedComponent //我们的WrappedComponent会被存放一个intl属性作为props,这是为什么上面的例子可以通过props直接获取到 {...this.props} {...{[intlPropName]: this.context.intl}}//如果有ref属性,那么返回我们的WrappedComponent实例 ref={withRef ? 'wrappedInstance' : null} /> ); } } return InjectIntl; }
问题:为什么要用这个InjectIntl而不是直接放在Context中,就像下面的例子就是直接放在context中的
import React, {PropTypes} from 'react'; import {intlShape, FormattedRelative} from 'react-intl'; const Component = ({date}, context) => ( //如果有ContextTypes,那么第二个参数就是我们的context <span title={context.intl.formatDate(date)}> //change here, use context directly <FormattedRelative value={date}/> </span> ); Component.propTypes = { date: PropTypes.any.isRequired, }; Component.contextTypes = { intl: intlShape.isRequired, } export default Component;
而下面的例子就是通过injectIntl来完成的
import React, {PropTypes} from 'react'; import {injectIntl, intlShape, FormattedRelative} from 'react-intl'; const Component = ({date, intl}) => ( <span title={intl.formatDate(date)}> <FormattedRelative value={date}/> </span> ); Component.propTypes = { date: PropTypes.any.isRequired, intl: intlShape.isRequired, }; export default injectIntl(Component);
injectIntl提供了一个非直接层,他的作用是解耦,使用React的intl来替代我们的React的context。如果以后React的context的API发生变化,代码的改变只要在injectIntl中进行就可以了,而不是在整个应用中
问题:方才说了第二个参数是context,我们看看都有哪些方法会传入第二个参数context
constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
componentDidUpdate(prevProps, prevState, prevContext)
问题:最后我们看看IntlProvider做了哪些内容
/* * Copyright 2015, Yahoo Inc. * Copyrights licensed under the New BSD License. * See the accompanying LICENSE file for terms. */ import {Component, Children, PropTypes} from 'react'; import IntlMessageFormat from 'intl-messageformat'; import IntlRelativeFormat from 'intl-relativeformat'; import IntlPluralFormat from '../plural'; import memoizeIntlConstructor from 'intl-format-cache'; import invariant from 'invariant'; import {shouldIntlComponentUpdate, filterProps} from '../utils'; import {intlConfigPropTypes, intlFormatPropTypes, intlShape} from '../types'; import * as format from '../format'; import {hasLocaleData} from '../locale-data-registry'; const intlConfigPropNames = Object.keys(intlConfigPropTypes); const intlFormatPropNames = Object.keys(intlFormatPropTypes); // These are not a static property on the `IntlProvider` class so the intl // config values can be inherited from an <IntlProvider> ancestor. const defaultProps = { formats : {}, messages: {}, textComponent: 'span', defaultLocale : 'en', defaultFormats: {}, }; export default class IntlProvider extends Component { static displayName = 'IntlProvider'; static contextTypes = { intl: intlShape, }; //获取父组件放在context中的intl属性 static childContextTypes = { intl: intlShape.isRequired, }; //同时把intl放在context中,那么所有的子组件都是可以使用的,其实是给injectIntl(Home)处理过的组件使用的 //injectIntl(Home)处理过的组件(也就是此处的Home)会获取父组件的context中保存的intl,并作为Props传递给Home static propTypes = { ...intlConfigPropTypes, children : PropTypes.element.isRequired, initialNow: PropTypes.any, }; //实例化的时候会采用如下调用方式:<IntlProvider locale={appLocale.locale} messages={appLocale.messages}/> //这的第二个参数是context要注意 constructor(props, context = {}) { super(props, context); invariant(typeof Intl !== 'undefined', '[React Intl] The `Intl` APIs must be available in the runtime, ' + 'and do not appear to be built-in. An `Intl` polyfill should be loaded.\n' + 'See: http://formatjs.io/guides/runtime-environments/' ); const {intl: intlContext} = context; // Used to stabilize time when performing an initial rendering so that // all relative times use the same reference "now" time. let initialNow; if (isFinite(props.initialNow)) { initialNow = Number(props.initialNow); } else { // When an `initialNow` isn't provided via `props`, look to see an // <IntlProvider> exists in the ancestry and call its `now()` // function to propagate its value for "now". initialNow = intlContext ? intlContext.now() : Date.now(); } // Creating `Intl*` formatters is expensive. If there's a parent // `<IntlProvider>`, then its formatters will be used. Otherwise, this // memoize the `Intl*` constructors and cache them for the lifecycle of // this IntlProvider instance. const {formatters = { getDateTimeFormat: memoizeIntlConstructor(Intl.DateTimeFormat), getNumberFormat : memoizeIntlConstructor(Intl.NumberFormat), getMessageFormat : memoizeIntlConstructor(IntlMessageFormat), getRelativeFormat: memoizeIntlConstructor(IntlRelativeFormat), getPluralFormat : memoizeIntlConstructor(IntlPluralFormat), }} = (intlContext || {}); this.state = { ...formatters, // Wrapper to provide stable "now" time for initial render. now: () => { return this._didDisplay ? Date.now() : initialNow; }, }; } getConfig() { const {intl: intlContext} = this.context; // Build a whitelisted config object from `props`, defaults, and // `context.intl`, if an <IntlProvider> exists in the ancestry. let config = filterProps(this.props, intlConfigPropNames, intlContext); // Apply default props. This must be applied last after the props have // been resolved and inherited from any <IntlProvider> in the ancestry. // This matches how React resolves `defaultProps`. for (let propName in defaultProps) { if (config[propName] === undefined) { config[propName] = defaultProps[propName]; } } if (!hasLocaleData(config.locale)) { const { locale, defaultLocale, defaultFormats, } = config; if (process.env.NODE_ENV !== 'production') { console.error( `[React Intl] Missing locale data for locale: "${locale}". ` + `Using default locale: "${defaultLocale}" as fallback.` ); } // Since there's no registered locale data for `locale`, this will // fallback to the `defaultLocale` to make sure things can render. // The `messages` are overridden to the `defaultProps` empty object // to maintain referential equality across re-renders. It's assumed // each <FormattedMessage> contains a `defaultMessage` prop. config = { ...config, locale : defaultLocale, formats : defaultFormats, messages: defaultProps.messages, }; } return config; } getBoundFormatFns(config, state) { return intlFormatPropNames.reduce((boundFormatFns, name) => { boundFormatFns[name] = format[name].bind(null, config, state); return boundFormatFns; }, {}); } getChildContext() { const config = this.getConfig(); // Bind intl factories and current config to the format functions. const boundFormatFns = this.getBoundFormatFns(config, this.state); const {now, ...formatters} = this.state; return { intl: { ...config, ...boundFormatFns, formatters, now, }, }; } shouldComponentUpdate(...next) { return shouldIntlComponentUpdate(this, ...next); } componentDidMount() { this._didDisplay = true; } render() { return Children.only(this.props.children); } }
参考资料:
runtime environment
在React项目中使用React-intl实现多语言支持
react-intl