JSX
为了便于阅读,建议将jsx拆分为多行,并且将内容用()包裹,这可以避免遇到自动插入分号陷阱。
注:自动插入分号ASI(auto semicon insertion)
在以 ([/+- 开头的语句前加分号
React DOM 使用 camelCase
(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。class 变为className , tabindex 变为tabIndex。
JSX可以防止 XSS(cross-site-scripting, 跨站脚本)攻击。
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。
以下两种代码完全等效:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
元素渲染
想要将一个元素渲染到跟DOM节点中,只需把它们一起传入 ReactDOM.render()
React 元素是不可变对象。
考虑 UI 在任意给定时刻的状态,而不是随时间变化的过程,能够消灭一整类的 bug。
组件&Props
React 元素也可以是用户自定义的组件
注意: 组件名称必须以大写字母开头。
从组件自身的角度命名 props,而不是依赖于调用组件的上下文命名。
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
纯函数:不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
function sum(a, b) {
return a + b;
}
State & 生命周期
react class方式声明:
- 先调用constructor初始化state
- 调用render函数渲染DOM元素
- 调用相关生命周期方法
当State中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。
正确渲染state:
-
调用setState()方法来更新state的状态,且构造函数是唯一可以给this.state赋值的地方
-
出于性能考虑,React 可能会把多个
setState()
调用合并成一个调用。this.setState(function(state, props) { return { counter: state.counter + props.increment }; });
-
当你调用
setState()
的时候,React 会把你提供的对象合并到当前的 state。组件State的更新是一个浅合并的过程。
任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
事件处理
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
如果需要组织事件的默认行为,必须显式的调用 preventDefault
引出
绑定事件处理函数作用域的方式
-
利用bind方法
constructor(props) { super(props); this.state = {isToggleOn: true}; // 为了在回调中使用 `this`,这个绑定是必不可少的 this.handleClick = this.handleClick.bind(this); }
-
你可以在回调中使用箭头函数,不推荐,每次会创建一个新的函数,当做参数传入子组件,可能会进行额外的重新渲染
// 此语法确保 `handleClick` 内的 `this` 已被绑定。 render() { return ( <button onClick={(e) => this.handleClick(e)}> Click me </button> ); }
-
class fields 语法
// 此语法确保 `handleClick` 内的 `this` 已被绑定。 // 注意: 这是 *实验性* 语法。 handleClick = () => { console.log('this is:', this); }
向事件处理程序传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
使用箭头函数绑定事件时,需要显示传递事件对象e
条件渲染
与运算符 &&
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
在 JavaScript 中,true && expression
总是会返回 expression
, 而 false && expression
总是会返回 false
。
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render
方法直接返回 null
,而不进行任何渲染。
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
在组件的 render
方法中返回 null
并不会影响组件的生命周期。
列表 & Key
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
给数组元素赋予确定标识的方式
-
使用元素的id
-
使用索引index。如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题。example
const todoItems = todos.map((todo, index) => // Only do this if items have no stable IDs <li key={index}> {todo.text} </li> );
元素的 key 只有放在就近的数组上下文中才有意义。即放在数组元素上,不论数组元素是原生标签还是自定义组件。
这个 key 不需要全局唯一,但在列表中需要保持唯一。
key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key
属性的值,请用其他属性名显式传递这个值:
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
如果一个 map()
嵌套了太多层级,那可能就是你提取组件的一个好时机。
引出
React 在以下两个假设的基础之上提出了一套 O(n) 的启发式算法:
- 两个不同类型的元素会产生出不同的树;
- 开发者可以通过
key
prop 来暗示哪些子元素在不同的渲染下能保持稳定;react tree diff 在处理层级移动时,会重新创建整个层级,影响性能。可以使用CSS显示或隐藏节点,而不是真的添加和删除节点。
在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。
表单
当需要处理多个 input
元素时,我们可以给每个元素添加 name
属性,并让处理函数根据 event.target.name
的值选择要执行的操作。
在受控组件上指定 value 的 prop 可以防止用户更改输入。如果指定了 value
,但输入仍可编辑,则可能是意外地将value
设置为 undefined
或 null
受控组件
- 设置value/checked属性
- 设置onChange事件
非受控组件
- 默认值通过defaultValue来进行设置
- 使用 ref 来从 DOM 节点中获取表单数据
在 React 中,<input type="file" />
始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
引出
使用受控组件的条件
- 就地反馈,如表单即时验证
- 除非所有的字段都有效,否则禁用提交按钮
- 强制执行特定的输入格式,如信用卡号码
refs
- 当
ref
属性用于 HTML 元素时,构造函数中使用React.createRef()
创建的ref
接收底层 DOM 元素作为其current
属性。 - 当
ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其current
属性。 - 你不能在函数组件上使用 ref 属性,因为他们没有实例。你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件
Ref 转发使组件可以像暴露自己的 ref 一样暴露子组件的 ref
回调方式
- 构造器中声明一个变量,在组件挂载完成后,调用生命周期方法,赋予这个变量对应的元素
通过React.createRef()方式创建
- 当 ref 被传递给
render
中的元素时,对该节点的引用可以在 ref 的current
属性中被访问。 - 通过在ref组件中调用 React.forwardRef()API实现ref的转发 ref传递
状态提升
你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
虽然提升 state 方式比双向绑定方式需要编写更多的“样板”代码,但带来的好处是,排查和隔离 bug 所需的工作量将会变少。
如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。
组合 vs 继承
特殊prop属性children ,类似于Vue中的插槽
实现具名插槽
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
常用于组件复用UI功能
对于非UI功能的复用,组件可以直接引入(import)而无需通过 extend 继承它们。