React的核心概念

作者:renzp94
时间:2021-04-07 11:46:33

ReactFacebook开发的一个声明式,高效且灵活的用于构建用户界面的Javascript库。主要有以下特点:

  • 声明式:以声明式编写 UI
  • 组件化:以组件的方式组织 UI
  • 单向数据流:数据只能从上到下传递。
  • JSXJSX是对Javascript语法的一种扩展,官方推荐使用此方式编写React应用,但不是必须的。
  • Virtual DOM:采用Virtual DOM来更新真实的DOM

Hello World

main.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>,
    document.getElementById('root')
);

App.jsx(函数)

import React from 'react';

function App() {
    return <div className="App">Hello World</div>;
}

export default App;

App.jsx(类)

import React, { Component } from 'react';

class App extends Component {
    render() {
        return <div>Hello World</div>;
    }
}

export default App;

React中,一般推荐使用JSX来编写代码,其文件后缀为.jsx.tsx,在文件中可以写HTML元素。在JSX文件中必须引入React,即import React from 'react'

通过React编写的组件最终需要通过react-dom提供的ReactDom.render来渲染。

JSX

JSXJavascript的语法扩展,可以在Javascript中写HTML。在JSX中通过{}可以插入Javascript变量或任何有效的Javascript表达式,也可以将JSX赋值给Javascript变量,还可以在HTML元素属性使用{}来设置属性值。

import React, { useState } from 'react';

function App() {
    const [count, setCount] = useState(0);
    const onClick = () => setCount(count + 1);

    return (
        <button
            style={{
                backgroundColor: '#fff',
                border: '1px solid #f2f2f2',
                outline: 'none'
            }}
            onClick={onClick}
        >
            点击{count}次
        </button>
    );
}

export default App;

注意:在JSX中需要元素的属性写成cameCase(小驼峰命名),如:元素的class属性写成className,tabindex写成tabIndex

事件处理

React元素的事件处理和DOM元素相似,但有几点不同:

  • React事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用JSX语法时需要传入一个函数作为事件处理函数,而不是一个字符串。
  • 不能通过返回false的阻止默认行为,必须通过e.preventDefault()来阻止默认行为。
  • 当使用class方式编写组件时,this需要手动绑定,否则this指向会出问题,可通过内联函数,在内联函数中调用,也可以使用箭头函数规避此问题。
  • 给事件函数传递参数时,如果需要合成事件e,则需要使用内联函数获取,然后再传递给事件函数。
import React, { Component } from 'react';

class App extends Component {
    constructor() {
        super();
        this.onClick1 = this.onClick1.bind(this);
    }

    onClick1() {
        console.log('onClick1:', this);
    }
    onClick2() {
        console.log('onClick2:', this);
    }
    onClick3 = () => {
        console.log('onClick3:', this);
    };
    onClick4 = (e, test) => {
        console.log('onClick4:', e, test);
    };

    render() {
        return (
            <div>
                <button onClick={this.onClick1}>onClick1</button>
                <button onClick={() => this.onClick2()}>onClick2</button>
                <button onClick={this.onClick3}>onClick3</button>
                <button onClick={this.onClick4}>onClick4</button>
                <button onClick={(e) => this.onClick4(e, 111)}>onClick5</button>
            </div>
        );
    }
}

export default App;

条件渲染

React中条件渲染非常简单,通过Javascript中的if三元运算来实现,如果不准备渲染任何内容可以设置为null

import React, { useState } from 'react';

function App() {
    const [visible, setVisible] = useState(true);
    const onClick = () => setVisible(!visible);

    let content = null;
    if (visible) {
        content = <div>显示的内容1</div>;
    } else {
        content = null;
    }

    return (
        <div>
            <button onClick={onClick}>改变状态</button>
            {content}
            {visible ? <div>显示的内容2</div> : null}
        </div>
    );
}

export default App;

列表渲染

React中通过将数据列表转为元素列表来实现列表渲染,一般可以通过Array.map来实现,在进行列表渲染时需要指定每一项的 key,且 key 应该是唯一的,以便数据发生更改时可以正确的渲染。

import React, { useState } from 'react';

function App() {
    const [list, setList] = useState([]);

    setTimeout(() => {
        setList([1, 2, 3, 4]);
    }, 2000);

    const elList = list.map((item) => <div key={`${item}_el`}>{item}</div>);

    return (
        <div>
            {list.length === 0 ? 'loading...' : list.map((item) => <div key={item}>{item}</div>)}
            {elList}
        </div>
    );
}

export default App;

受控组件

React中的state作为唯一的数据源,渲染表单的组件控制着用户输入时表单发生的操作,以此种方式控制取值的表单输入元素叫做受控组件。而表单的数据由DOM节点处理的组件叫做非受控组件。

import React, { ChangeEvent, useState } from 'react';

function App() {
    const [value, setValue] = useState('');
    const onChange = (e: ChangeEvent<HTMLInputElement>) => {
        setValue(e.target.value);
    };

    return (
        <div>
            <input type="text" value={value} onChange={onChange} />
            <p>输入的值为:{value}</p>
        </div>
    );
}

export default App;

组件

组件是将 UI 拆分为独立可复用的代码片段。React组件可以定义为class函数的形式

  • class:类需要继承于React.Component,且必须实现render函数,在render函数中返回要渲染的HTML
  • 函数:只需函数中返回要渲染的HTML即可。

State 和 Props

组件和函数类似,接受任意的属性(props),然后在组件中就可以使用了,有时组件内需要有自己的数据,称为状态(State)。在组件中,props是不可变的,只能通过父组件修改,如果需要在组件内修改prop则可通过传入一个修改函数,来修改父组件中的statestate不能直接修改,直接修改的话视图不会更新渲染,需要通过this.setState来修改,函数式组件需要通过Hooks来修改。如果state需要根据其他的state来更新的话,可以向setState传入一个函数,函数的第一个参数是state,第二个参数是prop

User.jsx

import React from 'react';

function User(props) {
    const onClick = () => {
        props.changeName('codebook');
    };
    return (
        <div onClick={onClick}>
            <img src={props.avatar} />
            <div>{props.nickname}</div>
        </div>
    );
}

export default User;

App.jsx

import React, { useState } from 'react';
import User from './components/User';

function App() {
    const [name, setName] = useState('React');

    return (
        <div>
            <User
                avatar="https://codebook.vercel.app/assets/images/logo.png"
                nickname={name}
                changeName={setName}
            />
        </div>
    );
}

export default App;

组件的生命周期

组件从创建到销毁的过程叫做生命周期。

生命周期图

每个组件都有以下生命周期(按照调用顺序排序):

组件创建时:

  • constructor():构造函数,组件挂载之前调用。
  • static getDerivedStateFromProps():在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  • render():渲染函数
  • componentDidMount():渲染完成函数。在组件挂载后(插入 DOM 树中)立即调用。

组件更新时:

  • static getDerivedStateFromProps()
  • shouldComponentUpdate():当propsstate发生变化时,在渲染执行之前被调用,首次渲染或使用forceUpdate()时不会调用该方法。默认返回true,如果返回false,则不会调用render()更新视图。
  • render()
  • getSnapshotBeforeUpdate():在最近一次渲染输出(提交到 DOM 节点)之前调用,任何返回值将作为参数传递给componentDidUpdate()
  • componentDidUpdate():在更新后会被立即调用,首次渲染不会执行此方法。

组件卸载时

  • componentWillUnmount():当组件从 DOM 中移除时会调用此方法。

组件发生错误时

  • static getDerivedStateFromError():在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新state
  • componentDidCatch():生命周期在后代组件抛出错误后(在“提交”阶段被调用,因此允许执行副作用)被调用。 它接收两个参数:
    • error:抛出的错误。
    • info:带有componentStack key的对象
import React, { Component } from 'react';
import User from './components/User';

class App extends Component {
    constructor() {
        console.log('constructor');
        super();

        this.state = {
            count: 0
        };
    }

    componentDidMount() {
        console.log('componentDidMount');
    }

    shouldComponentUpdate() {
        console.log('shouldComponentUpdate');
        return true;
    }

    getSnapshotBeforeUpdate() {
        console.log('getSnapshotBeforeUpdate');
        return null;
    }

    componentDidUpdate() {
        console.log('componentDidUpdate');
    }

    componentWillUnmount() {
        console.log('componentWillUnmount');
    }

    onClick = () =>
        this.setState({
            count: this.state.count + 1
        });

    render() {
        console.log('render');
        return <button onClick={this.onClick}>点击了{this.state.count}次</button>;
    }
}

export default App;

组件属性默认及验证

如果封装组件时需要指定组件默认,可通过defaultProps来指定,需要对组件属性做验证可通过propTypes并搭配prop-types库来验证

import React from 'react';
import PropTypes from 'prop-types';

interface ButtonProps {
    type: string;
    children: any;
}

const Button = (props: ButtonProps) => {
    return <button className={`button--${props.type}`}>{props.children}</button>;
};

Button.defaultProps = {
    type: 'default'
};

Button.propTypes = {
    type: PropTypes.string
};

const App = () => {
    return (
        <>
            <Button>默认按钮</Button>
            <Button type="primay">主要按钮</Button>
            <Button type="waring">错误按钮</Button>
            <Button type={1}>此按钮会在控制台报错,因为type类型不对</Button>
        </>
    );
};

export default App;

组合组件

React不建议组件继承,如果需要多个组件结合,可以尝试组合组件,将 UI 拆分成多个组件,通过组合形成不同的 UI。