React基本用法

2021.09/JavaScript

React是用于构建用户界面的JavaScript库,声明式编写UI,数据变动时React能高效更新并渲染合适的组件。

准备工作

使用Create React App创建项目。

npx create-react-app my-app

JSX

JSX标签语法是JavaScript的语法扩展,用来描述UI视图和事件逻辑等。Babel会把JSX转译成React.createElement()函数调用,创建React元素对象。

通过大括号{}嵌入变量或表达式,在属性中嵌入表达式时,不要在大括号外面加引号。

如果一个标签中无内容,可以使用/>闭合标签。

const element = <h1>Hello, {name}</h1>;
const element = <img src={user.avatarUrl} />;

JSX也是表达式,可以在if语句和for循环中使用JSX,将JSX赋值给变量,把JSX当作参数传入,以及在函数中返回JSX。

function getGreeting(user) {
if (user) return <h1>Hello, {formatName(user)}</h1>;
return <h1>Hello, Stranger</h1>;
}

JSX语法上更接近JavaScript而不是HTML,因而使用camelCase(小驼峰)来定义属性名称,class变成了className、for写成htmlFor、tabindex则为tabIndex。

为了便于阅读,将JSX拆分为多行,此时建议将内容包裹在括号()中。

const element = (
<h1 className="welcome">
Hello, {formatName(user)}!
</h1>
);

渲染

将React元素渲染到DOM节点中。

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('app'));

组件、Props

组件即可复用的代码片段,接受入参props,返回React元素。

组件名称必须以大写字母开头。

函数组件

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

class组件

class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

除了DOM标签,React元素也可以是自定义组件。当React元素为自定义组件时,会将JSX接收的属性(attributes)和子组件(children)合并成一个props对象传递给组件。

State、生命周期

React组件必须像纯函数一样保护其props的只读性。如果需要组件随用户操作、网络响应或者其他变化而动态更改输出内容,可以使用state。

class Clock extends React.Component {
constructor(props) {
// 使用props参数调用父类的构造函数
super(props);
// 在构造函数中为state赋初值,这里是唯一可以给state赋值的地方
this.state = { date: new Date() };
}

tick() {
// 调用setState(),让React知道state已变,React会重新调用render()
this.setState({ date: new Date() });
}

componentDidMount() {
// 可以在class中随意添加不参与数据流的额外字段,例如这里的timeId
this.timeId = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timeId);
}

render() {
const { date } = this.state;
return <p>{date.toLocaleTimeString()}</p>;
}
}

直接修改state不会重新渲染组件,而应该使用setState()。

出于性能考虑,React可能会把多个setState()调用合并成一个,这样this.props和this.state可能会异步更新。如果更新下一个状态时依赖这些值,需要setState()接收一个函数,这个函数用上一个state作为第一个参数,将此次更新被应用时的props做为第二个参数。

this.setState((state, props) => ({
counter: state.counter + props.increment
}));

组件的生命周期包括组件实例被创建并插入DOM中,组件的props或state发生变化时触发更新,以及组件从DOM中移除的过程。

挂载

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()
  • 更新

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()
  • 卸载

  • componentWillUnmount()
  • render()方法

    render()方法是class组件中唯一必须实现的方法,有多种返回类型。

  • 返回JSX创建的DOM节点或者自定义组件
  • 使用Fragment返回多个元素
  • 使用Portal将子节点渲染到父组件以外的DOM节点中
  • 将字符串或数字渲染为文本节点
  • 布尔类型或null则什么都不渲染
  • // 当不需要key或属性时,可使用短语法<>和</>
    function Glossary(props) {
    return (
    <dl>
    {props.items.map(item => (
    <React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.description}</dd>
    </React.Fragment>
    ))}
    </dl>
    );
    }

    对话框、悬浮卡以及提示框等场景,使用Portal将组件渲染到指定的DOM中。这样做不会影响事件冒泡。

    const modalRoot = document.getElementById('modal-root');

    class Modal extends React.Component {
    constructor(props) {
    super(props);
    this.el = document.createElement('div');
    }

    componentDidMount() {
    modalRoot.appendChild(this.el);
    }

    componentWillUnmount() {
    modalRoot.removeChild(this.el);
    }

    render() {
    return ReactDOM.createPortal(
    this.props.children,
    this.el
    );
    }
    }

    事件处理

    React事件命名采用camelCase(小驼峰),使用JSX时需传入一个函数用作事件处理。

    function Form() {
    function handleSubmit(e) {
    e.preventDefault();
    }

    return (
    <form onSubmit={handleSubmit}>
    <button type="submit">Submit</button>
    </form>
    );
    }

    JavaScript中,class的方法默认不会绑定this实例,解决办法有:

  • 在constructor()中使用bind为事件处理函数绑定实例
  • 在class中使用箭头函数定义方法
  • 在回调中使用箭头函数,此时如果该回调函数作为prop传入子组件时,可能会额外重新渲染
  • constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
    }
    class ClickButton extends React.Component {
    handleClick = () => { console.log(this); };

    render() {
    return (
    <button onClick={this.handleClick}>
    Click
    </button>
    );
    }
    }
    class ClickButton extends React.Component {
    handleClick() { console.log(this); }

    render() {
    return (
    <button onClick={() => this.handleClick()}>
    Click
    </button>
    );
    }
    }

    通过箭头函数或者bind为事件处理函数传递额外参数。下面两种情况,事件对象e都会被作为第二个参数传递。使用箭头函数时,事件对象必须显式传递,而bind方式的事件对象会被隐式传递。

    <button onClick={e => this.del(id, e)}>Delete</button>
    <button onClick={this.del.bind(this, id)}>Delete</button>

    Refs

    通过Refs访问DOM节点或者React组件实例。

    class CustomTextInput extends React.Component {
    constructor(props) {
    super(props);
    // 使用React.createRef()创建ref
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
    }

    focusTextInput() {
    // 通过ref的current属性访问DOM节点
    this.textInput.current.focus();
    }

    render() {
    // 通过ref属性将上面创建的ref附加到input节点
    return (
    <div>
    <input ref={this.textInput} />
    <button
    onClick={this.focusTextInput}
    >Focus the text input</button>
    </div>
    );
    }
    }
    class AutoFocusTextInput extends React.Component {
    constructor(props) {
    super(props);
    this.textInput = React.createRef();
    }

    componentDidMount() {
    // 通过ref的current属性访问组件实例
    this.textInput.current.focusTextInput();
    }

    render() {
    return <CustomTextInput ref={this.textInput} />;
    }
    }

    上面这种方式不能在函数组件上使用,因为它们没有实例。在函数组件上,可以使用forwardRef进行Refs转发,将ref通过函数组件传递到其子组件上。

    const FancyButton = React.forwardRef((props, ref) => (
    <button ref={ref}>{props.children}</button>
    ));

    // 中转获取button
    const ref = React.createRef();
    <FancyButton ref={ref}>Click me!</FancyButton>;

    在函数组件内部使用useRef。

    function CustomTextInput(props) {
    // 使用useRef创建ref
    const textInput = useRef(null);

    function focusTextInput() {
    textInput.current.focus();
    }

    return (
    <div>
    <input ref={textInput} />
    <button
    onClick={focusTextInput}
    >Focus the text input</button>
    </div>
    );
    }

    React也支持另一种设置Refs的方式,称为“回调Refs”。即传递一个函数,在这个函数中接受DOM节点或者组件实例作为参数,以使它们能在其他地方被存储和访问。

    class CustomTextInput extends React.Component {
    constructor(props) {
    super(props);
    this.textInput = null;
    this.setTextInputRef = element => {
    this.textInput = element;
    };
    this.focusTextInput = () => {
    if (this.textInput) this.textInput.focus();
    };
    }

    componentDidMount() {
    this.focusTextInput();
    }

    render() {
    return (
    <div>
    <input ref={this.setTextInputRef} />
    <button
    onClick={this.focusTextInput}
    >Focus the text input</button>
    </div>
    );
    }
    }

    表单

    在React中使用表单元素,有“受控组件”或者“非受控组件”两种形式。

    “受控组件”自己维护内部state,并根据用户输入等操作进行更新。

    “非受控组件”交由DOM节点自己处理,使用ref获取表单数据,可以通过defaultValue属性给表单元素设置初始值。

    class InfoForm extends Component {
    constructor(props) {
    super(props);
    this.state = {
    username: '',
    healthy: true,
    gender: 'male',
    city: ''
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    }

    handleChange({ target }) {
    this.setState({
    [target.name]: target.type === 'checkbox' ? target.checked : target.value
    });
    }

    handleSubmit(event) {
    console.log(this.state);
    event.preventDefault();
    }

    render() {
    return (
    <form onSubmit={this.handleSubmit}>
    <input
    name="username"
    placeholder="用户名"
    value={this.state.username}
    onChange={this.handleChange}
    />

    <label>
    是否健康
    <input
    name="healthy"
    type="checkbox"
    checked={this.state.healthy}
    onChange={this.handleChange}
    />
    </label>

    <label>
    <input
    name="gender"
    type="radio"
    value="male"
    checked={this.state.gender === 'male'}
    onChange={this.handleChange}
    />男
    </label>
    <label>
    <input
    name="gender"
    type="radio"
    value="female"
    checked={this.state.gender === 'female'}
    onChange={this.handleChange}
    />女
    </label>

    <select name="city" value={this.state.city} onChange={this.handleChange}>
    <option value="" disabled>请选择城市</option>
    <option value="BJ">北京</option>
    <option value="SH">上海</option>
    </select>

    <button type="submit">提交</button>
    </form>
    );
    }
    }
    京ICP备14036213号-2