React.PureComponent
,和 React.Component
相似,都是定义一个组件类。不一样是 React.Component
没有实现 shouldComponentUpdate()
,而 React.PureComponent
经过 props
和 state
的浅比较实现了。html
// React.PureComponent 纯组件
class Counter extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 0};
}
render() {
return (
<button onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>
);
}
}
复制代码
在下一节中将会详细介绍。前端
定义React组件的最简单方式就是定义一个函数组件,它接受单一的 props 并返回一个React元素。react
// 函数组件
function Counter(props) {
return <div>Counter: {props.count}</div>
}
// 类组件
class Counter extends React.Component {
render() {
return <div>Counter: {this.props.count}</div>
}
}
复制代码
受控和非受控主要是取决于组件是否受父级传入的 props 控制git
用 props 传入数据的话,组件能够被认为是受控(由于组件被父级传入的 props 控制)。github
数据只保存在组件内部的 state 的话,是非受控组件(由于外部没办法直接控制 state)。api
export default class AnForm extends React.Component {
state = {
name: ""
}
handleSubmitClick = () => {
console.log("非受控组件: ", this._name.value);
console.log("受控组件: ", this.state.name);
}
handleChange = (e) => {
this.setState({
name: e.target.value
})
}
render() {
return (
<form onSubmit={this.handleSubmitClick}>
<label>
非受控组件:
<input
type="text"
defaultValue="default"
ref={input => this._name = input}
/>
</label>
<label>
受控组件:
<input
type="text"
value={this.state.name}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
复制代码
与 html 不一样的是,在 React 中,<input>
或<select>
、<textarea>
等这类组件,不会主动维持自身状态,并根据用户输入进行更新。它们都要绑定一个onChange
事件;每当状态发生变化时,都要写入组件的 state 中,在 React 中被称为受控组件。数组
export default class AnForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ""};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return <input type="text" value={this.state.value} onChange={this.handleChange} />; } } 复制代码
onChange & value 模式(单选按钮和复选按钮对应的是 checked props)浏览器
react经过这种方式消除了组件的局部状态,使得应用的整个状态可控。性能优化
注意 <input type="file" />
,它是一个非受控组件。app
可使用计算属性名将多个类似的操做组合成一个。
this.setState({
[name]: value
});
复制代码
非受控组件再也不将数据保存在 state,而使用 refs,将真实数据保存在 DOM 中。
export default class AnForm extends Component {
handleSubmitClick = () => {
const name = this._name.value;
}
render() {
return (
<div> <input type="text" ref={input => this._name = input} /> <button onClick={this.handleSubmitClick}>Sign up</button> </div> ); } } 复制代码
非受控组件是最简单快速的实现方式,项目中出现极简的表单时,使用它,但受控组件才是是最权威的。
一般指定一个 defaultValue/defaultChecked 默认值来控制初始状态,不使用 value。
非受控组件相比于受控组件,更容易同时集成 React 和非 React 代码。
使用场景
特征 | 非受控组件 | 受控组件 |
---|---|---|
one-time value retrieval (e.g. on submit) | ✅ | ✅ |
validating on submit | ✅ | ✅ |
instant field validation | ❌ | ✅ |
conditionally disabling submit button | ❌ | ✅ |
enforcing input format | ❌ | ✅ |
several inputs for one piece of data | ❌ | ✅ |
dynamic inputs | ❌ | ✅ |
经过 state 管理状态
export default class Counter extends React.Component {
constructor(props) {
super(props)
this.state = { clicks: 0 }
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState(state => ({ clicks: state.clicks + 1 }))
}
render() {
return (
<Button onClick={this.handleClick} text={`You've clicked me ${this.state.clicks} times!`} /> ) } } 复制代码
输入输出数据彻底由props决定,并且不会产生任何反作用。
const Button = props =>
<button onClick={props.onClick}>
{props.text}
</button>
复制代码
展现组件指不关心数据是怎么加载和变更的,只关注于页面展现效果的组件。
class TodoList extends React.Component{
constructor(props){
super(props);
}
render(){
const {todos} = this.props;
return (<div> <ul> {todos.map((item,index)=>{ return <li key={item.id}>{item.name}</li> })} </ul> </div>)
}
}
复制代码
容器组件只关心数据是怎么加载和变更的,而不关注于页面展现效果。
//容器组件
class TodoListContainer extends React.Component{
constructor(props){
super(props);
this.state = {
todos:[]
}
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData();
}
fetchData(){
fetch('/api/todos').then(data =>{
this.setState({
todos:data
})
})
}
render(){
return (<div> <TodoList todos={this.state.todos} /> </div>) } } 复制代码
高阶函数的定义:接收函数做为输入,或者输出另外一个函数的一类函数,被称做高阶函数。
对于高阶组件,它描述的即是接受 React 组件做为输入,输出一个新的 React 组件的组件。
更通俗的描述为,高阶组件经过包裹(wrapped)被传入的 React 组件,通过一系列处理,最终返回一个相对加强(enhanced)的 React 组件,供其余组件调用。使咱们的代码更具备复用性、逻辑性和抽象特性,它能够对 render 方法作劫持,也能够控制 props 、state。
实现高阶组件的方法有如下两种:
// 属性代理
export default function withHeader(WrappedComponent) {
return class HOC extends React.Component { // 继承与 React.component
render() {
const newProps = {
test:'hoc'
}
// 透传props,而且传递新的newProps
return <div> <WrappedComponent {...this.props} {...newProps}/> </div> } } } // 反向继承 export default function (WrappedComponent) { return class Inheritance extends WrappedComponent { // 继承于被包裹的 React 组件 componentDidMount() { // 能够方便地获得state,作一些更深刻的修改。 console.log(this.state); } render() { return super.render(); } } } 复制代码
WrappedComponent.displayName || WrappedComponent.name || 'Component';
Hook 是 React 16.8 的新增特性。它可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。
但与 class 生命周期不一样的是,Hook 更接近于实现状态同步,而不是响应生命周期事件。
import React, { useState, useEffect } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
useEffect(()=>{
// 须要在 componentDidMount 执行的内容
return function cleanup() {
// 须要在 componentWillUnmount 执行的内容
}
}, [])
useEffect(() => {
// 在 componentDidMount,以及 count 更改时 componentDidUpdate 执行的内容
document.title = 'You clicked ' + count + ' times';
return () => {
// 须要在 count 更改时 componentDidUpdate(先于 document.title = ... 执行,遵照先清理后更新)
// 以及 componentWillUnmount 执行的内容
} // 当函数中 Cleanup 函数会按照在代码中定义的顺序前后执行,与函数自己的特性无关
}, [count]); // 仅在 count 更改时更新
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
);
}
复制代码
useLayoutEffect
与 componentDidMount
、componentDidUpdate
的调用阶段是同样的。可是,咱们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect
componentDidMount
或 componentDidUpdate
不一样的是,Hook 在浏览器完成布局与绘制以后,传给 useEffect
的函数会延迟调用,但会保证在任何新的渲染前执行useEffect
的思惟模型中,默认都是同步的。反作用变成了 React 数据流的一部分。对于每个 useEffect
调用,一旦你处理正确,你的组件可以更好地处理边缘状况。首先看一下 React.Component 结构
// ReactBaseClasses.js 文件
/** * Base class helpers for the updating state of a component. */
function Component(props, context, updater) {
this.props = props; // 属性 props
this.context = context; // 上下文 context
// If a component has string refs, we will assign a different object later.
// 初始化 refs,为 {},主要在 stringRef 中使用,将 stringRef 节点的实例挂载在 this.refs 上
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue; // updater
}
Component.prototype.isReactComponent = {};
/** * 设置 state 的子集,使用该方法更新 state,避免 state 的值为可突变的状态 * `shouldComponentUpdate`只是浅比较更新, * 可突变的类型可能致使 `shouldComponentUpdate` 返回 false,没法从新渲染 * Immutable.js 能够解决这个问题。它经过结构共享提供不可突变的,持久的集合: * 不可突变: 一旦建立,集合就不能在另外一个时间点改变。 * 持久性: 可使用原始集合和一个突变来建立新的集合。原始集合在新集合建立后仍然可用。 * 结构共享: 新集合尽量多的使用原始集合的结构来建立,以便将复制操做降至最少从而提高性能。 * * 并不能保证 `this.state` 经过 `setState` 后不可突变的更新,它可能还返回原来的数值 * 不能保证 `setrState` 会同步更新 `this.state` * `setState` 是经过队列形式来更新 state ,当 执行 `setState` 时, * 会把 state 浅合并后放入状态队列,而后批量执行,即它不是当即更新的。 * 不过,你能够在 callback 回调函数中获取最新的值 * * 注意:对于异步渲染,咱们应在 `getSnapshotBeforeUpdate` 中读取 `state`、`props`, * 而不是 `componentWillUpdate` * * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */
Component.prototype.setState = function(partialState, callback) {
// 当 partialState 状态为 object 或 function类型 或 null 时,
// 执行 this.updater.enqueueSetState 方法,不然报错
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
// 将 `setState` 事务放入队列中
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
/** * 强制更新,当且仅当当前不处于 DOM 事物(transaction)中才会被唤起 * This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * 默认状况下,当组件的state或props改变时,组件将从新渲染。 * 若是你的`render()`方法依赖于一些其余的数据, * 你能够告诉React组件须要经过调用`forceUpdate()`从新渲染。 * 调用`forceUpdate()`会致使组件跳过 `shouldComponentUpdate()`, * 直接调用 `render()`。但会调用 `componentWillUpdate` 和 `componentDidUpdate`。 * 这将触发组件的正常生命周期方法,包括每一个子组件的 shouldComponentUpdate() 方法。 * forceUpdate 就是从新 render 。 * 有些变量不在 state 上,当时你又想达到这个变量更新的时候,刷新 render ; * 或者 state 里的某个变量层次太深,更新的时候没有自动触发 render 。 * 这些时候均可以手动调用 forceUpdate 自动触发 render * * @param {?function} callback 更新完成后的回调函数. * @final * @protected */
Component.prototype.forceUpdate = function(callback) {
// updater 强制更新
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
复制代码
其中 this.refs
值 emptyObject
为:
// 设置 refs 初始值为 {}
const emptyObject = {};
if (__DEV__) {
Object.freeze(emptyObject); // __DEV__ 模式下, 冻结 emptyObject
}
// Object.freeze() 冻结一个对象,被冻结的对象不能被修改(添加,删除,
// 修改已有属性的可枚举性、可配置性、可写性与属性值,原型);返回和传入的参数相同的对象。
复制代码
ReactNoopUpdateQueue
为:
// ReactNoopUpdateQueue.js 文件
/** * 这是一个关于 更新队列(update queue) 的抽象 API */
const ReactNoopUpdateQueue = {
/** * 检查复合组件是否装载完成(被插入树中) * @param {ReactClass} publicInstance 测试实例单元 * @return {boolean} 装载完成为 true,不然为 false * @protected * @final */
isMounted: function(publicInstance) {
return false;
},
/** * 强制更新队列,当且仅当当前不处于 DOM 事物(transaction)中才会被唤起 * * 当 state 里的某个变量层次太深,更新的时候没有自动触发 render 。 * 这些时候就能够调用该方法强制更新队列 * * 该方法将跳过 `shouldComponentUpdate()`, 直接调用 `render()`, 但它会唤起 * `componentWillUpdate` 和 `componentDidUpdate`. * * @param {ReactClass} publicInstance 将被从新渲染的实例 * @param {?function} callback 组件更新后的回调函数. * @param {?string} callerName 在公共 API 调用该方法的函数名称. * @internal */
enqueueForceUpdate: function(publicInstance, callback, callerName) {
warnNoop(publicInstance, 'forceUpdate');
},
/** * 彻底替换state,与 `setState` 不一样的是,`setState` 是以修改和新增的方式改变 `state `的, * 不会改变没有涉及到的 `state`。 * 而 `enqueueReplaceState` 则用新的 `state` 彻底替换掉老 `state` * 使用它或 `setState` 来改变 state,而且应该把 this.state 设置为不可突变类型对象, * 而且this.state不会当即更改 * 咱们应该在回调函数 callback 中获取最新的 state * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} completeState Next state. * @param {?function} callback Called after component is updated. * @param {?string} callerName name of the calling function in the public API. * @internal */
enqueueReplaceState: function( publicInstance, completeState, callback, callerName, ) {
warnNoop(publicInstance, 'replaceState');
},
/** * 设置 state 的子集 * 它存在的惟一理由是 _pendingState 是内部方法。 * `enqueueSetState` 实现浅合并更新 `state` * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} partialState Next partial state to be merged with state. * @param {?function} callback Called after component is updated. * @param {?string} Name of the calling function in the public API. * @internal */
enqueueSetState: function( publicInstance, partialState, callback, callerName, ) {
warnNoop(publicInstance, 'setState');
},
};
export default ReactNoopUpdateQueue;
复制代码
注意,React API 只是简单的功能介绍,具体的实现是在 react-dom 中,这是由于不一样的平台,React API 是一致的,但不一样的平台,渲染的流程是不一样的,具体的 Component 渲染流程不一致,会根据具体的平台去定制。
组件生命周期请参考 Hooks 与 React 生命周期的关系
想看更过系列文章,点击前往 github 博客主页
走在最后,欢迎关注:前端瓶子君,每日更新