RN:组件

目录

一. 什么是组件
二. 组件的propsstate属性和普通属性
三. 组件的生命周期
四. 组件之间的通信方式
 1. 界面从前往后和从后往前传值:属性传值、回调函数传值
 2. 父组件给子组件传值、子组件给父组件传值、兄弟组件之间传值:属性传值、回调函数传值
 3. 跨层传值、一对多传值:通知
五. 如何自定义组件
六. 组件的导入与导出


一. 什么是组件


组件(Component)是指一个在UI上具备独立功能的单位,小的组件可以组合使用形成大的组件,最终完成整个App界面的搭建。它通常具备如下三个特征:

  • 可重用:一个组件就是一个具备独立功能的单位,它可以被用在代码的多个地方。
  • 可维护:一个组件就是一个具备独立功能的单位,它只包含它自身的逻辑,更容易被理解和维护。
  • 可组合:一个组件可以与另一个组件同级使用,一个组件也可以嵌套在另一个组件中使用。

一个组件本身的结构可以很简单,唯一必须的就是在render方法中return一个用JSX语法描述的组件,用来表明该组件应该被渲染成什么样子。(所谓JSX语法,是指在可以在JS中嵌入XML结构的语法)


二. 组件的propsstate属性和普通属性


我们每定义一个组件,这个组件就会自动拥有propsstate属性,因为我们自定义的组件都是继承自系统的Component组件,也就把Component组件的propsstate属性给继承下来了。此外我们也可以给一个组件直接添加一些普通属性

属性有定义、使用(如初始化、赋值、读取等)两个操作。

  • props

props用来存放组件的不可变属性,这些属性一般都是通过外界传进来的。我们不需要专门在组件内定义这些属性,而是在外界使用该组件的地方直接定义并赋值,至于别的使用,你可以在组件内部通过this.props.属性想怎么用怎么用。

// 组件
class CustomComponent extends Component {
    render() {
        return(
            <Text>{this.props.name + this.props.sex + this.props.age}</Text>
        );
    }
}

// 外界
class DetailPage extends Component<Props> {
    render() {
        return (
            <CustomComponent name={'张三'} sex={'男'} age={11}/>
        );
    }
}

注意:

外界在使用某个组件,给它的属性赋值时,外边一定要包一层大括号{}当然我们一敲等号,系统就自动给我们添加上这个大括号。这是因为我们需要把变量嵌入到JSX语法中,所以得用大括号括起来表示大括号里面是一个JS变量或表达式,需要执行后取值,因此我们可以把任意合法的JS表达式放进大括号里嵌入到JSX语法中。

既然说到props用来存放组件的“不可变属性”,这就说明这些属性不是像常量那样只能赋值一次,而是可以重新赋值的,也就是说你外界传进来的值变化了,这些属性的值自然就跟着变化了。所以通常情况下组件的props属性接收的是外界的state属性。

// 组件
class CustomComponent extends Component {
    render() {
        return(
            <Text>{this.props.name + this.props.sex + this.props.age}</Text>
        );
    }
}

// 外界
class DetailPage extends Component<Props> {
    constructor(props) {
        super(props);
        
        this.state = {
            name: '张三',
            sex: '男',
            age: 11,
        };
    }
    
    render() {
        return (
            <CustomComponent name={this.state.name} sex={this.state.sex} age={this.state.age}/>
        );
    }
}

这就说到了state属性,我们接下来就看看它。

  • state

state用来存放组件的可变属性,也就是说,state里面存放的属性可以在组件的生命周期内随时修改。而且state里属性的值一旦发生变化,就会触发组件的render方法来重新渲染该组件,换句话说,RN里一切界面的变化都是state里属性值的变化。

通常情况下,我们会在组件的constructor方法中,给this.state赋值来定义并初始化该组件的可变属性,什么地方要使用这个属性就直接this.state.属性使用它,什么地方要修改这个属性就调用this.setState()方法修改它。

constructor(props) {
    super(props);

    this.state = {
        // 定义并初始化一个属性
        color: 'red',
    };
}


// 使用属性
console.log(this.state.color);


// 通过setState()方法来修改属性的值
this.setState({
    color: 'red',
});

constructor方法里调用super方法,这个无需多言,前面已经说过了,是为了完成子类继承自父类的一些资源的初始化。但是这里有两个东西需要我们额外关心,那就是constructor方法和super方法都有一个参数props,为什么非要写这个参数,state又为什么要通过this.state = ......这样的方式来编写。

现在假设我们有一个Person类和一个Boy类。

class Person {
    name;

    constructor(name){
        this.name = name;
    }
}


class Boy extends Person {
    age;

    constructor(name, age) {
        super(name);

        this.age = age;
    }
}

可见我们的Boy类继承自Person类,Boy类也就继承了Person类的name属性。因此当我们在编写Boy类的constructor方法时,如果不把name属性传递给super方法,那Boy类的name属性是无法完成赋值的,因为name属性实际上是人家Person类的属性,name属性的赋值操作是在父类里面完成的,除非我们把name的也写在Boy类里面。

class Boy extends Person {
    age;

    constructor(name, age) {
        super();

        this.name = name;
        this.age = age;
    }
}

类推到这个地方,我们自定义的组件都是继承自系统的Component组件,自定义组件的props属性和state属性就是继承自Component组件。所以props属性就类似于上面的name属性,其真正的赋值过程是在父类Component中,所以我们会把自定义组件的props属性传递给super方法,否则很有可能导致自定义组件的props属性完不成赋值。而state就类似于上面的age属性,也许state属性确实也在Component中赋值了,但是我们需要在自定义组件里为组件添加可变属性啊,所以还是在自定义组件里重新写了一下赋值过程。综上,我们知道props属性和state属性在constructor其实可以有多种写法,但最常用的写法还是:

constructor(props) {
    super(props);

    this.state = {
        // 定义并初始化不可变属性
        // ......
    };
}
  • 普通属性

我们已经知道了props属性和state属性的适用场景,但是还有一种情况,例如组件内有个输入框,输入什么,组件的属性就记录什么,那这个属性改用props还是state呢?

很明显这个数据一直在变化,我们不能使用props,但是使用state好像也不对,因为该属性的值一直在变,就一直会触发组件的render()方法重新渲染组件,但是我们并不需要一直重新渲染组件啊,因此还有一种普通属性——直接定义在组件身上的属性。

// 组件
class CustomComponent extends Component {
    render() {
        return(
            <TextInput
                onChangeText={text => {
                    this.searchText = text;
                }}
            />
        );
    }
}

上面代码中searchText就是直接定义在组件身上——而非propsstate里——的一个普通属性,它可以随时变化,同时又不会触发render()方法。

可以看到我们也不用专门在组件内部定义它的普通属性,需要时直接.并赋值就行,因为我们知道JS对象新增一个属性的办法就是直接.并赋值,然后什么时候需要别的使用就可以随时使用了。

不过为了代码更加易读,我们会为这个类定义它的普通属性,然后再使用,直接定义在类的头部就可以了。

// 组件
class CustomComponent extends Component {
    searchText;
    
    render() {
        return(
            <TextInput
                onChangeText={text => {
                    this.searchText = text;
                }}
            />
        );
    }
}


三. 组件的生命周期


iOS里UIViewController提供了有- viewDidLoad- viewWillAppear:- viewDidAppear:- viewWillDisappear:- viewDidDisappear:- dealloc等生命周期方法,Android里Activity也有onCreate()onStart()onStop()onDestroy()等生命周期方法,这些生命周期方法展示了一个界面从创建到销毁的一生。RN里组件也有它的生命周期,分为三个阶段。

  • Mounting:加载阶段
  • Updating:更新阶段
  • Unmounting:卸载阶段

每个阶段也都对应着几个生命周期方法,如下。

下面介绍一下我们常用的几个生命周期方法:

  • constructor()方法

组件初始化时调用,类似于iOS里的- init方法,我们一般在这个方法里为组件添加并初始化一些可变的属性。

  • render()方法

组件渲染时调用,该函数必须实现,我们一般在这个方法里返回一个React组件,或者返回null和布尔值代表什么都不渲染。

该方法会在第一次渲染组件时调用,也会在组件stateprops里属性值发生变化时调用。

  • componentDidMount()方法

组件加载完毕时调用,类似于iOS里的- viewDidLoad方法,我们一般在这个方法里进行网络请求、写定时器等耗时操作。

  • componentWillUnmount()方法

组件即将卸载时调用,类似于iOS里的- viewWillDisappear:,我们一般在这个方法里移除定时器、移除通知等。

  • componentWillReceiveProps(object nextProps)方法

在当前组件props里的属性值发生变化时,会触发该方法,新的props可以从参数里取到,老的props可以通过this.props获取到。

该方法会在第一次渲染组件时不会被调用。

  • shouldComponentUpdate(object nextProps, object nextState):方法

组件接收到新的propsstate后,将要重新渲染之前调用(初次渲染不会调用,仅仅是更新时会调用)。我们可以根据实际情况来重写这个方法,灵活地控制当接收到新的propsstate时,组件是否要重新渲染,以此来减少不必要的性能损耗。


四. 组件之间的通信方式


自上而下的通信,采用属性;自下而上的通信,采用回调函数;跨层通信、一对多通信,采用通知。

1. 界面从前往后和从后往前传值:属性传值、回调函数传值

到目前为止,我们还没有学习界面的跳转与返回,下一篇文章将学习。这里先把界面从前往后和从后往前传值的方法列在这里,学习后可返回来查看。

其实RN里界面从前往后和从后往前传值和我们iOS里是一样的,我们先回想一下:

  • iOS里是如何从前往后传值的:属性传值,后一个界面定义一个接收数据的属性,前一个界面在跳转界面时把需要传递的数据传递给后一个界面的那个属性就可以了。

  • iOS里是如何从后往前传值的:使用block作为回调,后一个界面负责定义一个block属性,并在需要的地方调用block;前一个界面负责block的具体实现,并在跳转界面时把这个具体实现传递给后一个界面的block属性。

// 前一个界面
export default class TrendingPage extends Component<Props> {
    render() {
        return (
            <View>
                <Button
                    title={'跳转到详情页'}
                    onPress={() => {
                        NavigationUtil.navigate('DetailPage', {
                            data: '哈哈',
                            callback: (text) => this._callback(text)
                        });
                    }}
                />
            </View>
        );
    }

    _callback(text) {
        console.log(text);
    }
}

// 后一个界面
export default class DetailPage extends Component<Props> {
    render() {
        const {data, callback} = this.props.navigation.state.params;

        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>{data}</Text>

                <Button
                    title={'返回'}
                    onPress={() => {
                        callback('嘻嘻');
                        NavigationUtil.goBack();
                    }}
                />
            </View>
        );
    }
}

2. 父组件给子组件传值、子组件给父组件传值、兄弟组件之间传值:属性传值、回调函数传值

  • 父组件给子组件传值

这个和我们iOS里是一样的,我们iOS里父视图给子视图传值采用从前往后的传值法,我们先回想一下iOS里是如何从前往后传值的:属性传值,后一个界面定义一个接收数据的属性,前一个界面在跳转界面时把需要传递的数据传递给后一个界面的那个属性就可以了。

对应到RN里就是:子组件定义一个接收数据的属性,父组件在调用子组件时把需要传递的数据传递给子组件的那个属性就可以了。

// 子组件
class SubComponent extends Component {
    render() {
        return (
            null
        );
    }

    componentDidMount() {
        console.log(this.props.data);
    }
}

// 父组件
export default class ParentComponent extends Component {
    render() {
        return (
            <View style={styles.container}>
                <SubComponent
                    // 子组件定义一个接收数据的属性
                    // 父组件在调用子组件时把需要传递的数据传递给子组件的那个属性
                    data={'我是传递的数据哦'}
                />
            </View>
        );
    }
}
  • 子组件给父组件传值

这个和我们iOS里是一样的,我们iOS里子视图给父视图传值采用从后往前的传值法,我们先回想一下iOS里是如何从后往前传值的:使用block作为回调,后一个界面负责定义一个block属性,并在需要的地方调用block;前一个界面负责block的具体实现,并在跳转界面时把这个具体实现传递给后一个界面的block属性。

对应到RN里就是:子组件负责定义一个回调函数属性(定义在props里,因为它也是不可变的,外面传什么它就是什么),并在需要的地方调用回调函数;父组件负责回调函数的具体实现,并在调用子组件时把这个具体实现传递给子组件的回调函数属性。

// 子组件
class SubComponent extends Component {
    render() {
        return (
            <View style={styles.subComponent}>
                <TextInput
                    style={styles.textInput}
                    onChangeText={(text) => {
                        // 子组件在需要的地方调用回调函数
                        this.props.textDidChange(text);
                    }}
                />
            </View>
        );
    }
}

// 父组件
export default class ParentComponent extends Component {
    render() {
        return (
            <View style={styles.container}>
                <SubComponent
                    // 子组件负责定义一个回调函数属性
                    // 父组件负责调用子组件时把这个具体实现传递给子组件的回调函数属性
                    textDidChange={(text) => this._textDidChange(text)}
                />
            </View>
        );
    }

    // 父组件负责回调函数的具体实现
    _textDidChange(text) {
        console.log(text);
    }
}
  • 兄弟组件之间传值

兄弟组件之间传值的本质其实是:兄弟组件共享父组件的state + 子组件给父组件传值 + 父组件.setState()重新触发render() + 父组件给子组件传值。

// 子组件1
class SubComponent1 extends Component {
    render() {
        return (
            <View style={styles.subComponent}>
                <Text>{this.props.data}</Text>
                <TextInput
                    style={styles.textInput}
                    onChangeText={(text) => {
                        this.props.textDidChange(text);
                    }}
                />
            </View>
        );
    }
}
// 子组件2
class SubComponent2 extends Component {
    render() {
        return (
            <View style={styles.subComponent}>
                <Text>{this.props.data}</Text>
                <TextInput
                    style={styles.textInput}
                    onChangeText={(text) => {
                        this.props.textDidChange(text);
                    }}
                />
            </View>
        );
    }
}

// 父组件
export default class ParentComponent extends Component {
    constructor(props) {
        super(props);

        this.state = {
            subComponent1Data: null,
            subComponent2Data: null,
        }
    }

    render() {
        return (
            <View style={styles.container}>
                <SubComponent1
                    data={this.state.subComponent1Data}
                    textDidChange={(data) => this._textDidChange1(data)}
                />

                <SubComponent2
                    data={this.state.subComponent2Data}
                    textDidChange={(data) => this._textDidChange2(data)}
                />
            </View>
        );
    }

    _textDidChange1(data) {
        this.setState({
            subComponent2Data: data,
        });
    }

    _textDidChange2(data) {
        this.setState({
            subComponent1Data: data,
        });
    }
}

3. 跨层传值、一对多传值:通知

上面讲的几种情况都是组件之间有关联的传值,我们在开发中当然还可能遇见无关联组件之间传值——即我们通常所说的跨层传值,也有可能遇到一对多传值,那该怎么做呢?

回想iOS中类似的情况,我们是通过可以通过单例和通知来做的,RN中好像用通知比较多一些。RN也提供了一个专门负责通知的组件DeviceEventEmitter,它类似于我们iOS里的通知中心[NSNotificationCenter defaultCenter],需要接收和发送通知的双方都要导入它。

通知的使用其实也很简单,我们按着iOS写通知的思路来就没问题,不具体分析了,下面只简单写个例子。

// 界面二,想要接收界面一发出的某个通知
import {DeviceEventEmitter} from 'react-native';

class MyPage extends Component<Props> {
    render() {
        // ......
    }

    componentDidMount() {
        // 在通知中心注册观察者
        this.listener = DeviceEventEmitter.addListener(
            // 通知的名字
            'FavoritePageDidTouch',
            // 通知的回调
            (notificationData) => {
                console.log(notificationData);
            });
    }

    componentWillUnmount() {
        // 移除通知的观察者
        if (this.listener) {
            this.listener.remove();
        }
    }
}
// 界面一,发出某个通知
import {DeviceEventEmitter} from 'react-native';

class FavoritePage extends Component<Props> {
    render() {
        return (
            <View style={styles.container}>
                <Button
                    title={'发出通知'}
                    onPress={() =>
                        // 发出通知,以及通知携带的参数
                        DeviceEventEmitter.emit('FavoritePageDidTouch', {
                            'hi': '11'
                        })
                    }
                />
            </View>
        );
    }
}


五. 如何自定义组件


接下来我们以自定义一个导航栏组件为例,来看一下在RN中如何自定义一个组件。

-----------ProjectNavigationBar.js-----------

/**
 * 自定义导航栏
 *
 * 至于为什么要自定义导航栏,详见DynamicBottomNavigator.js,DynamicBottomNavigator.navigationOptions = ......那里有提到
 */

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View, StatusBar, ViewPropTypes} from 'react-native';
import {PropTypes} from 'prop-types';
import Color from "../../Const/Color";

// 高度
const STATUS_BAR_HEIGHT = (Platform.OS === 'ios' ? 20 : 0);// 安卓自己保留了StatusBar的高度,我们不设置了
const NAVIGATION_BAR_HEIGHT = (Platform.OS === 'ios' ? 44 : 50);

// StatusBar其实是系统提供的组件,这里我们在StatusBarShape里写的属性,其实和StatusBar的属性一模一样,方便我们传递给它
const StatusBarShape = {// 注意整个项目的StatusBar只可能有一种配置,后设置的会覆盖先设置的
    barStyle: PropTypes.oneOf(['default', 'light-content', 'dark-content']),
    hidden: PropTypes.bool,
};

export default class ProjectNavigationBar extends Component {
    // 为该组件的属性设置类型检查:一个组件有很多属性,有些属性我们想给它指定为特定的类型,就是通过这种办法。设置之后,外界在使用该组件时,如果给某个属性赋的值的类型,跟我们这里类型检查设置的类型不一样,编译器就会报警告。
    // 注意:这里仅仅是对组件的部分属性做了类型检查,而不是添加属性(添加属性还是在外界使用组件的时候,那里写什么就是添加了什么属性),也就是一个组件的属性是要做类型检查属性的父集
    static propTypes = {
        // 每个组件不是都有个style属性嘛,它也必须是某种特定的类型————ViewPropTypes.style
        style: ViewPropTypes.style,

        // 我们封装的导航栏组件也包含了状态栏,它们统称为导航栏,这里这个属性其实只是用来配置statusBar的,而不是直接接收一个statusBar组件
        statusBarProps: PropTypes.shape(StatusBarShape),

        // 导航栏标题
        title: PropTypes.string,
        // 导航栏标题位置的自定义组件
        titleView: PropTypes.element,
        // titleView的style
        titleViewStyle: ViewPropTypes.style,

        // 左item
        leftButton: PropTypes.element,
        // 右item
        rightButton: PropTypes.element,

        // 导航栏是否隐藏
        hide: PropTypes.bool,
    };

    // 为该组件的属性设置默认值:一个组件有很多属性,有些属性我们想给它设置默认值,就是通过这种办法。
    // 注意:static defaultProps和static propTypes一样,它们是兄弟关系,各自负责不同的功能,不存在谁包含谁,这里仅仅是为该组件的部分属性设置了默认值而已,也不是添加属性(添加属性还是在外界使用组件的时候,那里写什么就是添加了什么属性),也就是说一个组件的属性是要设置默认值的属性的父集
    static defaultProps = {
        // 这里我们只设置一下statusBarProps的默认值,如果用户不设置statusBar的属性的话,我们让项目的statusBar的属性有个默认值
        statusBarProps: {
            barStyle: 'light-content',
            hidden: false,
        },
    };


    render() {
        // 创建状态栏
        let statusBar = this.props.statusBarProps.hidden ? null :
            <View style={styles.statusBar}>
                <StatusBar {...this.props.statusBarProps}/>
            </View>;

        // 创建title或titleView
        let titleView = this.props.titleView ? this.props.titleView :
            <Text
                style={styles.title}
                numberOfLines={1}
                // 一行显示不下时,省略号的模式,'head'前省略,'tail'后省略,'middle'中间省略,'clip'裁切能显示的部分显示、而不显示省略号
                ellipsizeMode={'tail'}
            >
                {this.props.title}
            </Text>;

        // 创建导航栏
        let navigationBar = this.props.hide ? null :
            <View style={styles.navigationBar}>
                {/* 创建左item */}
                {this.getButtonElement(this.props.leftButton)}

                {/* 中间title或titleView */}
                <View style={[styles.navigationBarTitleContainer, this.props.titleViewStyle]}>
                    {titleView}
                </View>

                {/* 创建右item */}
                {this.getButtonElement(this.props.rightButton)}
            </View>;

        return (
            // 注意:this.props.style是外界传进来的style,一定要放在styles.container我们内部定义的style后面,否则外面设置的覆盖不了前面的,用户设置的就没效果了
            <View style={[styles.container, this.props.style]}>
                {/* 状态栏 */}
                {statusBar}
                {/* 导航栏 */}
                {navigationBar}
            </View>
        );
    }

    getButtonElement(element) {
        // 外界已经写好leftButton和rightButton传进来了,这里我们为了好布局它们俩,给它俩外面包一层View来做它俩的style
        return (
            <View style={styles.navigationBarButton}>
                {element ? element : null}
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        backgroundColor: Color.THEME_COLOR,
    },

    statusBar: {
        height: STATUS_BAR_HEIGHT,
    },

    navigationBar: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
        height: NAVIGATION_BAR_HEIGHT,
    },

    navigationBarTitleContainer: {
        alignItems: 'center',
        justifyContent: 'center',

        // 为了避免leftButton和rightButton大小不一样,而导致我们的titleView没法横向居中,这里我们用一下绝对布局
        position: 'absolute',
        top: 0,
        right: 40,
        bottom: 0,
        left: 40,
    },

    title: {
        fontSize: 17,
        color: 'white',
    },

    navigationBarButton: {
        alignItems: 'center',
    },
});

附注:属性的类型检查

先在项目中导入prop-types库:yarn add prop-types

属性是某个JS数据类型
  • PropTypes.bool
  • PropTypes.number
  • PropTypes.string
  • PropTypes.array
  • PropTypes.object
  • PropTypes.func
属性是某个RN组件
  • PropTypes.element
属性是某几个特定值中的一个(枚举值)
  • PropTypes.oneOf(['value1', 'value2'])
属性是一个包含指定形状属性的JS对象

即该属性是一个JS对象,它只能包含shape(obj)方法的参数obj里指定的属性,也就是说属性要和obj长得差不多,而且属性里的属性要少于等于obj里的属性。

  • PropTypes.shape(obj)
属性可以是任意类型
  • PropTypes.any
属性为必需填写

上述语法,都可以通过在后面加上isRequired来表明该属性是必需填写的。如果将属性声明为必需的,如果使用时没有给该属性传递数据,手机上会弹出相关的警告信息。

  • PropTypes.array.isRequired

更多类型检查,详见使用 PropTypes 进行类型检查


六. 组件的导入与导出


组件主要有两个命令:exportimportexport关键字用来导出当前组件的接口供外界使用,import关键字用来导入外界组件的接口。

1. export关键字

一个组件就是一个独立的文件,也就是说JS会把每一个.js文件看做是一个组件。而该文件内部所有的变量,外部都无法获取,如果你希望外部能够读取该组件内部的某个变量,就必须使用export关键字导出该变量。

比如下面是一个JS文件,里面就使用了export命令导出变量。

// profile.js

let firstName = '三';
let lastName = '张';
let year = 2000;

export {firstName, lastName, year};

export关键字除了可以导出变量,还可以导出一个JS文件里的方法和类。(其实一个JS文件里,也就只能直接定义变量、方法和类这三个东西了)

function hello() {}
// export hello;// 报错
export {hello};// 正确


class Person {}
class Son extends Person {}
export {Person, Son};

需要注意的是,使用export关键字导出东西的时候,一定要记得后面加上大括号{},即便只导出一个东西也要加,否则会报错。

2. import关键字

当我们使用export关键字将某个组件的接口导出之后,其他的JS文件就可以通过import关键字去导入那个组件导出的接口了。

比如下面是main.js文件从profile.js文件里导入数据。

// main.js
import {firstName, lastName, year} from './profile'

function printFullName() {
    console.log(firstName + ' ' + lastName);
}

需要注意的是:

  • 使用import关键字导入东西的时候,最好也是在后面加上一个大括号{}
  • import导入的数据都是只读的,不允许修改,你一修改就报错,但如果导入的变量是个对象,我们不能直接修改对象,但是修改对象的属性是不会报错的。
  • from后面是组件文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。

3. export default命令

export default命令用来指定某个组件的默认输出,一个组件内只能有一个默认输出,所以一个组件内只能使用一次export default命令。

下面我们看下为什么要搞这个export default命令。

经过上面第1、2小节,我们看到使用import命令的时候,需要知道另一个组件里导出了哪些东西,也就是说需要知道这些东西的变量名、函数名或类名,否则就没办法无法导入了。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解那个组件导出了哪些变量、函数或类。

因此为了给用户提供方便,让他们不用阅读文档就能加载组件,就可以用到export default命令,为某个组件指定默认输出。

// export-default.js
export default function () {
  console.log('foo');
}

上面代码是一个组件文件export-default.js,它的默认输出就是一个函数,其他组件加载该组件时就不需要知道它具体导出了什么,可以使用import命令为该匿名函数指定任意名字。

// import-default.js
import customName from './export-default';
customName(); // 'foo'

需要注意的是: 使用export default导出的东西,在被import导入时,不需要也不能加大括号{}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容