React学习笔记(二) 组件

479 阅读4分钟

组件允许我们将代码拆分为独立可复用的代码片段,这是一个十分重要的概念

1、函数组件

我们可以通过编写 JavaScript 函数定义组件

<!DOCTYPE html>
<html>

<head>
    <title>Demo</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone"></script>
</head>

<body>
    <div id="app"></div>

    <script type="text/babel">
        // 通过 function 定义组件,函数返回一个 React 元素
        // 需要注意的是,组件名称必须以大写字母开头
        // 因为 React 会将以小写字母开头的组件视为原生 DOM 标签
        function SayHello() {
            return <h1>Hello World</h1>;
        };

        // React 元素不仅仅是 DOM 标签,也可以是自定义组件
        const element = <SayHello />;

        ReactDOM.render(
            element,
            document.getElementById('app')
        );
    </script>
</body>

</html>

2、class 组件

我们也可以通过使用 class 定义组件,并且推荐使用这种方式

因为使用 class 定义组件允许我们为组件添加一些额外的方法和属性,使得组件具有更好的拓展性

<!DOCTYPE html>
<html>

<head>
    <title>Demo</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="https://unpkg.com/babel-standalone"></script>
</head>

<body>
    <div id="app"></div>

    <script type="text/babel">
        // class 组件必须继承 React.Component
        class SayHello extends React.Component {
            // 使用 render() 函数返回一个 React 元素
            // 注意,该元素必须只能具有一个顶层标签
            render() {
                return <h1>Hello World</h1>;
            }
        };

        // 使用组件的方法是一样的
        const element = <SayHello />;

        ReactDOM.render(
            element,
            document.getElementById('app')
        );
    </script>
</body>

</html>

3、props

当 React 元素为自定义组件时,JSX 接收的属性会转换为单个对象传递给组件,我们将这个对象称之为 props

通过这种方式,我们可以给组件传递数据

class SayHello extends React.Component {
	constructor(props) {
		// 为什么要调用 super?
        // 如果不调用 super,那就无法在构造函数中访问 this
        // 为什么要传入 props?
        // 如果不传入 props,那就无法在构造函数中访问 this.props
        super(props);
        console.log(this);
        console.log(this.props);
	}
    
    render() {
        // 在组件中通过 this.props 访问 props
        // 注意,props 应该是只读的,也就是说我们不应该修改它的取值
        return <h1>Hello { this.props.name }</h1>;
    }
};

// 这里将属性转换为 props 对象并传入组件
const element = <SayHello name='Alice' />;

ReactDOM.render(
    element,
    document.getElementById('app')
);

4、自定义函数

通过自定义函数,我们可以在 class 组件中添加额外的方法

class SayHello extends React.Component {
    // 构造函数
	constructor(props) {
        // 将 props 传递到父类构造函数
		super(props);
	}
    
    // 自定义函数
    format(name) {
        return name.substring(0,1).toUpperCase() + name.substring(1);
    }
    
    render() {
        // 通过 this.functionName() 调用自定义函数
        let formatedName = this.format(this.props.name);
    	return <h1>Hello { formatedName }</h1>;
    }
};

const element = <SayHello name='alice'/>;

ReactDOM.render(
    element,
    document.getElementById('app')
);

5、state

通过 state,我们可以在 class 组件中添加额外的数据

state 与 props 都是组件的数据,不同之处在于 props 是从父组件传入的数据,state 是本组件私有的数据

class Timer extends React.Component {
	constructor(props) {
		super(props);
        // 初始化 state
		this.state = { date: new Date() };
	}

    render() {
        // 通过 this.state 访问 state
        return (
            <div>
        	    <h1>Hello</h1>
                <p>现在是{ this.state.date.toLocaleTimeString() }</p>
            </div>
        )
    }
};

const element = <Timer />;

ReactDOM.render(
    element,
    document.getElementById('app')
);

使用 state 有几个需要特别注意的地方:

  • 不要直接修改 state

我们可以直接访问 state,但是不能直接修改 state,例如

this.state.date = new Date()
// 这样的语句并不会重新渲染组件

要想修改 state 并重新渲染组件,我们可以使用 this.setState(),例如

this.setState({date: new Date()})

但是构造函数是唯一特别的地方,我们可以直接在构造函数里面给 this.state 赋值,例如

this.state = { date: new Date() }
  • state 的更新是异步的

出于性能的考虑,React 可能会把多个 setState() 调用合并成一个调用

因此,this.propsthis.state 可能会异步更新,所以不要依赖它们的值来更新 state

this.setState({
	counter: this.state.counter + this.props.increment
})
// 这样的代码可能不会正常更新 counter

要解决这个问题,可以让 setState() 接收一个函数作为参数,而不是一个对象

这个函数的第一个参数为上一个 state,第二个参数为此次更新被应用时的 props,返回一个对象

this.setState((state, props) => ({
	counter: state.counter + props.increment
}));
  • state 的更新会被合并

setState() 调用会把提供的对象合并到当前 state,而非替换

也就是说,假如我们在构造函数中初始化 state 如下:

constructor(props) {
    super(props);
    this.state = {
        name: 'Alice',
        phone: '12345679810'
    };
}

然后调用 setState() 方法更新 state 如下:

this.setState({
	phone: '10987654321'
})

此时,state 中的数据应该是 { name: 'Alice', phone: '10987654321'},而不是 { phone: '10987654321' }

6、生命周期

每个组件都有生命周期,都会包含生命周期方法,我们可以重写这些方法,使组件在特定阶段完成特定操作

组件的生命周期大体可以分为三个阶段,每个阶段调用生命周期方法的顺序如下:

挂载:当组件插入到 DOM 中时触发

  • constructor():在组件挂载前调用,主要用于初始化 state 或为事件处理函数绑定实例

    这里不要使用 setState() 方法,如果需要初始化 state,可以直接给 this.state 赋值

  • render():class 组件中唯一必须实现的方法,该函数应该为纯函数

    也就是说在不修改 state 的情况下,每次调用返回相同的结果,并且它不与浏览器进行交互

  • componentDidMount():在组件挂载后调用,主要进行依赖于 DOM 结点的初始化操作

    例如实例化请求或添加订阅,这里可以使用 setState() 方法

更新:当组件的 props 或 state 发生改变时触发

  • render()

  • componentDidUpdate():在更新之后调用,但是首次渲染不会执行

    这里可以进行 DOM 操作,并且可以使用 setState() 方法,但必须包含在一个条件语句里面

卸载:当组件从 DOM 中移除时触发

  • componentWillUnmount():在组件卸载之前调用

    这里不应使用 setState() 方法,因为组件卸载之后,将永远不会重新渲染,也不会重新挂载

class Timer extends React.Component {
    // 生命周期方法,在挂载前调用
	constructor(props) {
		super(props);
        // 初始化 state
		this.state = { date: new Date() };
	}
    
    // 自定义方法
    tick() {
        // 通过 this.setState() 修改 state
        this.setState({
            date: new Date()
        })
    }
    
    // 生命周期方法,在挂载后调用
    componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000)
    }
    
    // 生命周期方法,在卸载前调用
    componentWillUnmount() {
        clearInterval(this.timerID)
    }

    // 生命周期方法,在挂载或更新时调用
    render() {
        // 通过 this.state 访问 state
        return (
            <div>
        	    <h1>Hello</h1>
                <p>现在是{ this.state.date.toLocaleTimeString() }</p>
            </div>
        )
    }
};

const element = <Timer />;

ReactDOM.render(
    element,
    document.getElementById('app')
);