Open sfsoul opened 4 years ago
错误边界无法捕获事件处理器内部的错误。与 render
方法和生命周期方法不同,事件处理器不会在渲染期间触发。因此若它们抛出异常,React 仍然能够知道需要在屏幕上显示什么。若需要在事件处理器内部捕获错误,使用 JS 的 try/catch
语句。
无状态组件(Stateless Component)是最基础的组件形式,由于没有状态的影响所以就是纯静态展示的作用。它的基本组成结构就是属性(props)加上一个回调函数。由于不涉及到状态的更新,所以这种组件的复用性也最强。
export const Header = (props) => {
return (
<div>无状态组件</div>
)
}
在无状态组件的基础上,若组件内部包含状态(state)且状态会随着事件或者外部的消息而发生改变的时候,就构成了有状态组件(Stateful Component)。有状态组件通常会带有生命周期(lifeCycle),用以在不同的时刻触发状态的更新。
export class Home extends Component {
constructor(props) {
super(props);
}
render() {
return (
<Header />
)
}
}
Refs 提供了一种方式,允许访问DOM节点或在 render 方法中创建的 React 元素。
Refs 使用 React.createRef()
创建的,并通过 ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
render() {
return <div ref={this.myRef} />;
}
}
当 ref 被传递给 render
中的元素时,对该节点的引用可以在 ref 的 current
属性中访问。
const node = this.myRef.current;
ref 的值根据节点的类型而有所不同:
ref
属性用于 HTML 元素时,构造函数中使用 React.createRef()
创建的 ref
接收底层 DOM 元素作为其 current
属性;ref
属性用于自定义 class 组件时,ref
对线下接收组件的挂载实例作为其 current
属性;ref
属性,因为它们没有实例。 默认情况下,不能在函数组件上使用 ref
属性,因为它们没有实例:
function MyFunctionComponent() {
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// This will *not* work!
return (
<MyFunctionComponent ref={this.textInput} />
);
}
}
可以在函数组件内部使用 ref
属性,只要它指向一个 DOM 元素或 class 组件:
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor
render()
componentDidMount()
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
shouldComponentUpdate()
render()
componentDidUpdate()
当组件从 DOM 中移除时会调用:
componentWillUnmount()
render()
方法是 class 组件中唯一必须实现的方法。
render()
函数应该为纯函数,意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。如需与浏览器进行交互,请在 componentDidMount()
或其他生命周期方法中执行你的操作。保存 render()
为纯函数,可以使组件更容易思考。若 shouldComponentUpdate()
返回 false,则不会调用 render()
。
在 constructor
函数中 不要调用 setState()
方法。若组件需要使用内部 state
,直接在构造函数中为 this.state
赋值初始 state
:
constructor(props) {
super(props);
// 不要在这里调用 this.setState()
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this);
}
避免将 props
的值复制给 state
,这是个常见错误:
constructor(props) {
super(props);
// 不要这样做
this.state = { color: props.color };
}
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。此方法是比较适合添加订阅的地方,若添加了订阅,请不要忘记在 componentWillUnmount()
里取消订阅。可以在 componentDIdMount()
里直接调用 setState()
。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render()
两次调用的情况下,用户也不会看到中间状态。
componentDidUpdate
会在更新后被立即调用。首次渲染不会执行此方法。当组件更新后,可以在此处对 DOM 进行操作。若对更新前后的 props
进行了比较,也可以选择在此处进行网络请求(当 props
未发生变化时,则不会执行网络请求)。
componentDidUpdate(prevProps) {
// 典型用法(不要忘记比较 props):
if (this.props.userID !== prevProps.userID) {
this.fetchData(this.props.userID);
}
}
可在 componentDidUpdate()
中直接调用 setState()
,但请注意它必须被包裹在一个条件语句里,否则会导致死循环。它会导致额外的重新渲染,影响组件性能。
componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如:清除 timer,取消网络请求或清除在 componentDidMount
中创建的订阅等。componentWillUnmount
中不应该调用 setState()
,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。
setState()
将对组件 state
的更改排入队列,并通知 React 需要使用更新后的 state
重新渲染此组件及其子组件。将 setState()
视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state
的变更会立即生效。setState()
并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState()
后立即读取 this.state
成为了隐患。为了消除隐患,请使用 componentDidUpdate
或者 setState
的回调函数(setState(updater, callback)
),这两种方式都可以保证在应用更新后触发。
默认情况下,当组件的 state
或 props
发生变化时,组件将重新渲染。若 render()
方法依赖于其他数据,则可以调用 forceUpdate()
强制让组件重新渲染。调用 forceUpdate()
将致使组件调用 render()
方法,此操作会跳过该组件的 shouldComponentUpdate()
。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate()
方法。若标记发生变化,React 仍将只更新 DOM。
表单数据将交由 DOM 节点来处理。
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,可以使用 ref 来从 DOM 节点中获取表单数据。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
通过指定一个 defaultValue
属性来赋予组件一个初始值。
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
Props 的只读性
组件无论是使用函数声明还是通过 class 声明,都决不能修改自身的
props
。所有 React 组件都必须像纯函数一样保护它们的props
不被更改。正确地使用 State
不要直接修改 State
应该使用
setState()
:State 的更新可能是异步的
出于性能考虑,React 可能会把多个
setState()
调用合并成一个调用。 因为this.props
和this.state
可能会异步更新,所以不要依赖它们的值来更新下一个状态。要解决这个问题,让
setState()
接收一个函数而不是一个对象。此函数用上一个state
作为第一个参数,将此次更新被应用时的props
作为第二个参数:State 的更新会被合并
调用
setState()
时,React 会把你提供的对象合并到当前的state
。向事件处理程序传递参数
在循环中通常会为事件处理函数传递额外的参数。
在这两种情况下,React 的事件对象
e
会被作为第二个参数传递。若通过箭头函数的方式,事件对象必须显式的进行传递;而通过bind
的方式,事件对象以及更多的参数将会被隐式的进行传递。阻止组件渲染
希望能隐藏组件,即使它已经被其他组件渲染。可以让
render
方法直接返回null
,而不进行任何渲染。在组件的render
方法中返回null
并不会影响组件的生命周期。componentDidUpdate
依然会被调用。用 key 提取组件
元素的
key
只有放在就近的数组上下文中才有意义。一个好的经验法则是:在
map()
方法中的元素需要设置key
属性。状态提升
在 React 应用中,任何可变数据应当只有一个相对应的唯一 "数据源"。通常,state 都是首先添加到需要渲染数据的组件中去。然后如果其他组件也需要这个 state,可以将它提升至这些组件的最近共同父组件中。应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。