今天(指2天)把 React 文档高级指引部分看完了。感觉 React 文档写的很分散,顺序太乱了,看完感觉没学到什么。
再把 hook 部分看完,就去看慕课网的教程了。
1. 生命周期
挂载期:
- constructor(),而且此时已经可以用原型方法了
- render()
- 渲染 DOM,组件挂载,添加 ref
- componentDidMount() 钩子函数触发 更新期:
- 通过setState(),更新 props 等方式触发更新
- shouldComponentUpdate(),默认返回 true,可以自定义判断返回 false 来阻止后面的步骤
- 如果上面返回 true,则执行 render()
- React 更新 DOM 和 ref
- componentDidUpdate() 卸载期:
- componentWillUnmount()
2. context
context 用于存放大量的组件都需要的数据,如用户信息,语言,等。
const MyContext = React.createContext(); // 只能在外面定义
class App extends React.Component {
render(
<MyContext.provider value="想传的context的值">
<OtherComponent1 /> // 被 provider 包裹住的组件都能使用 context
<OtherComponent2 />
<OtherComponent3 />
</MyContext>
);
}
。。。 // 假设这中间有一大堆中间组件,都无需传递这个 context 了
// 然后↓这是需要使用 context 的组件
class ContextNeededComponent extends React.Component {
static contextType = MyContext; // 实验性语法
render(
{this.context} // 如果设置了 contextType 就可以通过 this.context 访问了
);
}
ContextNeededComponent.contextType = MyContext; // 设置 contextType 普通语法
context 会让代码复用性变差,如果只是想避免每个中间件都传一大堆参数的话,可以用组合先把这一大堆参数组合成一个变量,再把这个变量传下去,这样每个中间件也只需要传一个参数了。如果要修改变量的值,也无需修改中间件。
如果要在函数式组件中使用 context,则需要用 Consumer。
如果 value 的值发生变化,则所有使用这个 value 的 Comsumer 都会变化。
如果 value 传的是一个对象,则会有性能问题,因为每次 provider 重新渲染的时候,value 都会被赋值为一个新对象,如:
<MyContext.provider value = { { name: "Alice" } }>
每次渲染的时候,都会新生成一个 { name: "Alice" } 对象,然后被赋值给 value,而 JS 会认为这是两个不同的值(不同的地址),然后重新渲染所有的 Comsumer 组件。
为了解决这个问题,可以把 {name: "Alice"} 放在 state 里,然后 value 传入 state:
constructor(props) {
super(props);
this.state = {
myObject: { name: "Alice" }
}
}
/* 省略各种代码 */
<MyContext.provider value = { this.state.myObject }>
3. 错误边界
暂时跳过,这是文档。
4. Refs
Refs 用于超出自上而下的数据流之外,访问 DOM 节点或者 React 元素。
Refs 比较适合用于①管理焦点,文本选择,媒体播放②强制播放动画③集成第三方DOM库。
如果可以不用 refs,最好就不用 refs。
4.1 refs 使用
4.1.1 html 元素的 ref
class MyComponent extends React.Component {
construtor(props) {
super(props);
this.myRef = React.createRef(); // 创建 ref
}
focusOnInput() {
this.myRef.current.focus(); // 通过 this.myRef 访问 render() 中的这个 input 元素
}
render() {
return (<input ref={this.myRef}></input>); // 通过特殊命名属性绑定
}
}
4.1.2 class 组件的 ref
用法一样
4.1.3 函数式组件无法使用 ref
函数式组件无法使用 ref,ref 是和组件实例绑定的,而函数式组件没有实例。
不过可以在函数式组件里使用绑定在别的元素上的 ref,需要一些额外操作。
4.1.4 回调 ref
可以更精细的调控 ref,参考这篇文章。
4.2 ref 转发
之前的 ref 都只能在本组件内使用,如果想把 ref 传递给子组件,则需要用到 ref 转发。
// 使用 React.forwardRef 创建组件,ref 作为第二个参数传入
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton" onClick={props.onClick}>
{props.children}
</button>
));
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 普通的创建 ref
this.myRef = React.createRef();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this.myRef);
}
render() {
return (
<div>
// 把 myRef 传给子组件
<FancyButton ref={this.myRef} onClick={this.handleClick}>Click me!</FancyButton>
</div>
)
}
}
下图可以看到 ref 绑定的确实是 button,而不是 FancyButton。绑定到 FancyButton 的 ref 被转发到了 button 上。
5. 非受控组件
受控组件需要对输入框写一个 onChange 函数,每次值更改都会改变 state。
但有时候我们并不需要对输入值做特别复杂的处理,这时候可以使用非受控组件减少很多代码量。
非受控组件的意思是不受 state 控制,相当于 DOM 操作。
5.1 使用
非受控组件使用 ref 指向对应的输入框
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef(); // 使用 ref
}
handleSubmit(event) {
/* DO Something*/
this.input.current.value; // 提交的时候对输入框做一些处理
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" defaultValue="12" ref={this.input} /> // 还可以添加默认值
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
由于添加 value 值会变成只读,因此需要一个方法添加表单的默认值。在输入框里是 defaultValue,选择框则是 defaultChecked。
6. fragment
由于组件 render 只能返回一个元素,有时候想返回多个元素只能用<div>包裹起来,但这显然会引起很多问题。因此 React 引入了 fragment 解决这个问题。
<React.fragment>
<li></li>
<li></li>
</React.fragment>
可以用短语法来代替,效果和上面一致
<>
<li></li>
<li></li>
</>
7. JSX 深入
7.1 背后是 createElement 语法
JSX 引用的是 React.createElement(Component, props, ...children) 语法。(props 为 object)
对应的是: <Component {...props}>...children</Component> JSX 语法。(props 为展开)。自闭合标签则没有 children 参数。
7.2 点语法
标签名可以是通过点语法引用的,比如下面这样:
const MyComponents = {
MyFirstComponent: function MyFirstComponent (props) {/* DO SOMETHING */}
}
<MyComponents.MyFirstComponent /> // 是可以这么用的,只要这个点语法确实是指向一个组件
7.3 标签名不能是通用表达式
虽然可以用点语法,但是动态表达式是不可以的:
<MyComponent[0] /> // 报错
需要先赋值给一个大写字母开头的变量才行。
const Myxxx = MyComponent[0];
<Myxxx /> // 可以
7.4 不赋值默认为 true
比如 <MyInput autocomplete> 等价于 <MyInput autocomplete={true}>
通常建议后者写法,因为会和 es6 对象简写混淆。
7.5 props 展开
就这样→<MyComponent {...props}>。
可以通过展开把一些不要的属性移除。
const {removedProp, ...otherProps} = this.props;
<MyOtherComponent {...otherProps} /> // 把 removedProp 移除后的 props 们。
7.6 JSX 中的子元素
- 包含在组件中的子元素会作为{props.children}传给组件。
- 组件虽然不能返回多个元素,但是可以返回一个多个元素组成的数组,无需被 fragment 包裹
- 子元素可以通过大括号表示的表达式返回任意数据类型:
如图所示,函数可以被传入 props.children,不过不能渲染出来,可以传给组件做别的处理。 - null,undefined,false 等值会被无视,如果要传,只能先转为字符串。
8. shouldComponentUpdate
参见这里,注意不可变部分。尤其是在使用 setState 的时候,千万不要通过直接更改原state的值来更改,这会导致 shouldComponentUpdate 出问题,因为此时下一个 state 和上一个 state 是同一个对象,即使数据改变了也会被当做没有改变。多用 concat 这种方式创建新对象。
或者使用第三方库。
9. Portals
参见原文。
10. diffing 算法
diffing 是一个启发式算法,建立在以下两个假设之上:
- 不同类型的元素生成的树不一样。
- 通过 key 可以保持子元素复用。 diffing 算法:
- 对比不同类型的元素:如果两个元素类型不一样,则直接销毁该节点(以及子节点)并建立一个新的元素。即使子节点是一样的,这些子节点也会被销毁并被重新建立。(这使用了假设1)
- 如果类型是一样的,则保留节点,只修改属性。修改完属性后,继续递归到子节点。
- 组件修改属性的时候,组件实例保持不变,props 被修改,重新 render()。
- 在递归子节点的时候,React 会同时递归更新前后的节点,当发现差异的时候,生成一个 mutation。
- 如果子元素列表前后有很多元素是一样的,但是顺序发生了很大改变(尤其是把其中一个元素放到了第一个),那么元素会被重新创建,这样会有性能损耗。因此需要引入 key。(使用假设2)
- 当子元素有 key 的时候,React 就知道哪些元素是移动过的了。
- 一般来说,如果元素本身就有 id,那就可以用。如果没有,则可以新增一个 id 字段,或者根据元素一部分内容的 hash 值生成。key 的唯一性只需要在这个列表中唯一即可。
- 如果设置 key,则会默认用数组下标。这会引起很多问题,不只是性能,有时候还会导致 state 值的篡改。
11. render props/HOC
都是为了增加代码复用性的技巧,见文档吧。
12. 严格模式
不是 JS 的严格模式,React 自己也有专门的严格模式。
被 <React.StrictMode> 包围的元素都会以严格模式进行。<React.StrictMode> 本身不作渲染,和 fragment 一样。严格模式只会在开发版本有效。
<React.StrictMode>
/*balabala*/
</ React.StrictMode>
效果如下,主要还是检测过时 API:
- 不安全的生命周期函数
- 过时的字符串 ref API
- 废弃的 findDOMNode
- 检测意外的副作用:故意重复调用一些生命周期函数,帮助检测潜在问题。
- 过时的 context API
13. PropTypes
从 V15.5 开始,使用 PropTypes 都要导入 prop-types 库(毕竟官方推荐用 flow 或者 typescript 进行类型检查)。
propTypes 是 react 自带的一个类型检查功能,用法如下:
import PropTypes from "prop-types" // 导入
class MyComponent extends React.Component {
render() {return ( <div>{this.props.name}</div> );}
}
MyComponent.PropTypes = {
name: PropTypes.string // 限制
}
可以很直观看出对 name 限定了字符串类型,如果不是的话控制台会警告。
13.1 可以限定的类型
有哪些类型可以限制可以参考这里。
13.2 限定 props.children 唯一元素
可以限定传过来的 props.children 只能有一个元素
MyComponent.propTypes ={ // 这里第一个字母小写
children: PropTypes.element.isRequired; // 这里第一个字母大写
}
isRequired 限制了如果没有传入该 prop,也会显示警告。
13.3 通过 defaultProps 设定 props 的默认值
用法如下:
MyComponent.defaultProps = {
name: "alice",
age: 25
}
如上可以设置 props 的默认值,这会消除 isRequired 的限制,因为首先通过 defaultProps 赋值,然后才会被 propTypes 检查。
函数组件的使用有一点点小区别。