React文档阅读笔记

组件

props 被用来在组件之间传递数据,是不可以变的,也不应该被改变。

组件属性

React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称。比如 class 变成了 className,而 tabindex 则对应着 tabIndex。

可以传递任何字符串常量或者{}包裹的JavaScript表达式作为属性值。当传递一个字符串常量时,该值会被解析为HTML非转义字符串,<MyComponent message="&lt;3" /><MyComponent message={"<3"} />是等价的

如果没有给属性传值,它默认为 true。因此下面两种用法是等价的:<MyTextBox autocomplete /><MyTextBox autocomplete={true} />不建议使用默认值,应该显式指定

组件属性中的的 key 和 ref 不会出现在 props 中, 如果组件中需要使用与key相同的值,可以通过自定义属性传递。

组件的 key 可以在某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此在渲染一组组件的时候,应当给数组中的每一个元素赋予唯一的标识,这个表示在这一组元素中应该是独一无二的。

1
2
3
4
5
6
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);

组件的渲染

React 组件都是 immutable 不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。即便我们每秒都创建了一个描述整个UI树的新元素,React DOM 也只会更新渲染文本节点中发生变化的内容。

falsenullundefinedtrue 作为子组件的时候,它们不会直接被渲染。下面的表达式是等价的:

1
2
3
4
5
6
<div />
<div></div>
<div>{false}</div>
<div>{null}</div>
<div>{undefined}</div>
<div>{true}</div>

通过这个特性,我们可以对 React 组件进行条件渲染,写出如下的代码:

1
2
3
<div>
{showHeader && <Header />}
</div>

需要注意的是,上面的代码在showHeader的值为0的时候也是成立的,这时候就会渲染出0,所以在这样使用的时候,要确保 && 前面的表达式始终为布尔值

如果需要类似falsenullundefinedtrue 可以渲染,需要转换为字符串。

事件处理

在 React 中,事件绑定的是一个{}包裹起来的函数,而不是一个字符串,当需要阻止事件的默认行为时,必须明确使用preventDefault

在 React 的事件处理中,event 对象是SyntheticEvent的实例,这是一个合成对象,它对原生的事件对象做了兼容性的封装,并拥有和原生事件对象相同的属性和方法,包括 stopPropagation()preventDefault(),但是没有浏览器兼容问题。如果因为一些因素,需要底层的浏览器事件对象,只要使用nativeEvent属性就可以获取到它了。

event 对象会在事件处理函数执行完成后,对象将会被重用,并且所有属性会被置空。如果想以一个异步的方式来访问事件属性,需要调用event.persist()。这样会在池中删除合成事件,并且在用户代码中保留对事件的引用。

state

  1. 直接更新 state 不会触发重新渲染,需要通过 setState 更新
  2. React 通过将多个 setState 调用合并成一个调用来提高性能, 所以在 setState 之后直接通过this.state.xxx获取属性值,可能不及预期。
  3. 通过 setState 传入函数的方法,可以修复 2 中的问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 方法1
    this.setState((prevState, props) => ({
    counter: prevState.counter + props.increment
    }));
    // this.state.xxxx 可以获取预期值
    // 方法2
    this.setState({
    counter: prevState.counter + props.increment
    },()=>{
    // this.state.xxxx 可以获取预期值
    });
  4. 通过 setState() 单独更新一个属性的时候,React 将自动合并属性值到当前的 state。

Refs

Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。使用 ref 的时候,可以通过 defaultValue 设置定初始值。

ref的值

React 会在组件加载时将 DOM 元素传入 current 属性,在卸载时则会改回 null。ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前。

  1. 当 ref 属性被用于一个普通的 HTML 元素时,React.createRef() 将接收底层 DOM 元素作为它的 current 属性以创建 ref 。
  2. 当 ref 属性被用于一个自定义类组件时,ref 对象将接收该组件已挂载的实例作为它的 current 。
  3. 你不能在函数式组件上使用 ref 属性,因为它们没有实例。但是,依然可以在函数式组件内部使用 ref , 只要它指向一个 DOM 元素或者 class 组件即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    function CustomTextInput(props) {
    // 这里必须声明 textInput,这样 ref 回调才可以引用它
    let textInput = null;

    function handleClick() {
    textInput.focus();
    }

    return (
    <div>
    <input
    type="text"
    ref={(input) => {
    textInput = input;
    }} />

    <input
    type="button"
    value="Focus the text input"
    onClick={handleClick}
    />
    </div>
    );
    }

何时使用 Refs

如果可以通过声明式实现,则尽量避免使用 refs。

下面是几个适合使用 refs 的情况:

  • 处理焦点、文本选择或媒体控制。
  • 触发强制动画。
  • 集成第三方 DOM 库

如何使用refs

  1. 16.3版本之后可以使用 React.createRef() 创建 refs, 通过 current 获取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class MyComponent extends React.Component {
    constructor(props) {
    super(props);
    this.myRef = React.createRef();
    }
    click(){
    const node = this.myRef.current;
    }
    render() {
    return <div onClick={this.click} ref={this.myRef} />;
    }
    }
  2. 回调 Refs

    不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,以存储它们并使它们能被其他地方访问。

    React 将在组件挂载时将 DOM 元素传入ref 回调函数并调用,当卸载时传入 null 并调用它。ref 回调函数会在 componentDidMout 和 componentDidUpdate 生命周期函数前被调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class CustomTextInput extends React.Component {
    constructor(props) {
    super(props);

    this.textInput = null;

    this.setTextInputRef = element => {
    this.textInput = element;
    };

    this.focusTextInput = () => {
    // 直接使用原生 API 使 text 输入框获得焦点
    if (this.textInput) this.textInput.focus();
    };
    }

    componentDidMount() {
    // 渲染后文本框自动获得焦点
    this.focusTextInput();
    }

    render() {
    // 使用 `ref` 的回调将 text 输入框的 DOM 节点存储到 React
    // 实例上(比如 this.textInput)
    return (
    <div>
    <input
    type="text"
    ref={this.setTextInputRef}
    />
    <input
    type="button"
    value="Focus the text input"
    onClick={this.focusTextInput}
    />
    </div>
    );
    }
    }
  3. String 类型的 Refs,不建议使用,已过时,并在未来可能被移除。现有代码建议使用回调函数的方式替代

    字符形式的ref有个需要注意的问题是在哪里渲染,就会挂载在那个组件身上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class MyComponent extends React.Component {
    constructor(props) {
    super(props);
    }
    click(){
    const node = this.refs.ref;
    }
    render() {
    return <div onClick={this.click} ref="ref" />;
    }
    }

如果 ref 回调以内联函数的方式定义,在更新期间它会被调用两次,第一次参数是 null ,之后参数是 DOM 元素。这是因为在每次渲染中都会创建一个新的函数实例。因此,React 需要清理旧的 ref 并且设置新的。通过将 ref 的回调函数定义成类的绑定函数的方式可以避免上述问题,但是大多数情况下无关紧要。

Portals

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。

1
2
// 第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素。
ReactDOM.createPortal(child, container)

通常讲,当你从组件的 render 方法返回一个元素,该元素仅能装配 DOM 节点中离其最近的父元素:

1
2
3
4
5
6
7
8
render() {
// React mounts a new div and renders the children into it
return (
<div>
{this.props.children}
</div>
);
}

然而,有时候将其插入到 DOM 节点的不同位置也是有用的,对于 portal 的一个典型用例是当父组件有 overflow: hidden 或 z-index 样式,但你需要子组件能够在视觉上“跳出(break out)”其容器。例如,对话框、hovercards以及提示框:

1
2
3
4
5
6
7
8
render() {
// React does *not* create a new div. It renders the children into `domNode`.
// `domNode` is any valid DOM node, regardless of its location in the DOM.
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}

尽管 portal 可以被放置在 DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致。如上下文特性依然能够如之前一样正确地工作,无论其子节点是否是 portal,由于 portal 仍存在于 React 树中,而不用考虑其在 DOM 树中的位置。

这包含事件冒泡。一个从 portal 内部会触发的事件会一直冒泡至包含 React 树 的祖先。

受控组件

在React中,<input type="file" /> 始终是一个不受控制的组件,因为它的值只能由用户设置,而不是以编程方式设置。以下示例显示如何创建ref节点以访问提交处理程序中的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.files[0].name}`
);
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input
type="file"
ref={input => {
this.fileInput = input;
}}

/>

</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}

ReactDOM.render(
<FileInput />,
document.getElementById('root')
);

<input type="text"><textarea>, 和 <select> 都十分类似 - 他们都通过传入一个value属性来实现对组件的控制。

在react项目开发中,input标签经常使用onChange方法获取输入值改变state:<input type="text" id="redeemNum" value={state.num} onChange={(e) => this.inputChange(e.target.value)}/>
但是,在IE9下发现 e.target.value 取值一直为undefined。在IE中,e.target 指的是window,查阅React文档发现:对于 <input><textarea>,onChange通常应该用代替DOM的内置onInput事件进行处理函数。
解决方法:<input type="text" id="redeemNum" value={state.num} onInput={(e) => this.inputChange(e.target.value)}/>

扩展

JSX

JSXJavaScript XML的简写,本质上是React.createElement(component,props,...children)的语法糖,通过在线 Babel 编译器可以很清楚的看到JSX实现的细节。

大写开头的 JSX 标签表示一个 React 组件,小写开头的 JSX 标签表示一个 html 规范中的元素,表达式无法作为 jsx 标签出现<components[props.storyType] story={props.story} />,这种是错误的

JSX 防注入攻击

你可以放心地在 JSX 当中使用用户输入:

1
2
3
const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;

React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。