组件
props 被用来在组件之间传递数据,是不可以变的,也不应该被改变。
组件属性
React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称。比如 class 变成了 className,而 tabindex 则对应着 tabIndex。
可以传递任何字符串常量或者{}
包裹的JavaScript表达式作为属性值。当传递一个字符串常量时,该值会被解析为HTML非转义字符串,<MyComponent message="<3" />
和<MyComponent message={"<3"} />
是等价的
如果没有给属性传值,它默认为 true。因此下面两种用法是等价的:<MyTextBox autocomplete />
和<MyTextBox autocomplete={true} />
,不建议使用默认值,应该显式指定。
组件属性中的的 key 和 ref 不会出现在 props 中, 如果组件中需要使用与key相同的值,可以通过自定义属性传递。
组件的 key 可以在某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此在渲染一组组件的时候,应当给数组中的每一个元素赋予唯一的标识,这个表示在这一组元素中应该是独一无二的。
1 | const numbers = [1, 2, 3, 4, 5]; |
组件的渲染
React 组件都是 immutable 不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。即便我们每秒都创建了一个描述整个UI树的新元素,React DOM 也只会更新渲染文本节点中发生变化的内容。
false
、null
、undefined
和 true
作为子组件的时候,它们不会直接被渲染。下面的表达式是等价的: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,所以在这样使用的时候,要确保 && 前面的表达式始终为布尔值
如果需要类似false
、null
、undefined
和 true
可以渲染,需要转换为字符串。
事件处理
在 React 中,事件绑定的是一个{}
包裹起来的函数,而不是一个字符串,当需要阻止事件的默认行为时,必须明确使用preventDefault
。
在 React 的事件处理中,event 对象是SyntheticEvent
的实例,这是一个合成对象,它对原生的事件对象做了兼容性的封装,并拥有和原生事件对象相同的属性和方法,包括 stopPropagation()
和 preventDefault()
,但是没有浏览器兼容问题。如果因为一些因素,需要底层的浏览器事件对象,只要使用nativeEvent
属性就可以获取到它了。
event 对象会在事件处理函数执行完成后,对象将会被重用,并且所有属性会被置空。如果想以一个异步的方式来访问事件属性,需要调用event.persist()
。这样会在池中删除合成事件,并且在用户代码中保留对事件的引用。
state
- 直接更新 state 不会触发重新渲染,需要通过 setState 更新
- React 通过将多个 setState 调用合并成一个调用来提高性能, 所以在 setState 之后直接通过
this.state.xxx
获取属性值,可能不及预期。 通过 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 可以获取预期值
});通过 setState() 单独更新一个属性的时候,React 将自动合并属性值到当前的 state。
Refs
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素。使用 ref 的时候,可以通过 defaultValue 设置定初始值。
ref的值
React 会在组件加载时将 DOM 元素传入 current 属性,在卸载时则会改回 null。ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前。
- 当 ref 属性被用于一个普通的 HTML 元素时,
React.createRef()
将接收底层 DOM 元素作为它的 current 属性以创建 ref 。 - 当 ref 属性被用于一个自定义类组件时,ref 对象将接收该组件已挂载的实例作为它的 current 。
- 你不能在函数式组件上使用 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
24function 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
16.3版本之后可以使用 React.createRef() 创建 refs, 通过 current 获取
1
2
3
4
5
6
7
8
9
10
11
12class 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} />;
}
}回调 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
39class 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>
);
}
}String 类型的 Refs,不建议使用,已过时,并在未来可能被移除。现有代码建议使用回调函数的方式替代
字符形式的ref有个需要注意的问题是在哪里渲染,就会挂载在那个组件身上
1
2
3
4
5
6
7
8
9
10
11class 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
8render() {
// 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 | render() { |
尽管 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
37class 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
JSX
是JavaScript XML
的简写,本质上是React.createElement(component,props,...children)
的语法糖,通过在线 Babel 编译器可以很清楚的看到JSX
实现的细节。
大写开头的 JSX 标签表示一个 React 组件,小写开头的 JSX 标签表示一个 html 规范中的元素,表达式无法作为 jsx 标签出现<components[props.storyType] story={props.story} />
,这种是错误的
JSX 防注入攻击
你可以放心地在 JSX 当中使用用户输入:
1 | const title = response.potentiallyMaliciousInput; |
React DOM 在渲染之前默认会 过滤
所有传入的值。它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击。