React 升级到 16.2.0 使用记录
说明
1. 函数定义组件与类定义组件
针对有复杂状态或者有多个事件处理函数的组件尽量使用 ‘类定义组件’;而对于简单的展示型组件,可以使用函数定义组件,减少代码量,函数的第一个参数是传入的 props
// 类定义组件
import React, { Component } from 'react';
import {withRouter} from 'react-router-dom';
import '../../scss/home.scss';
@withRouter
export default class TestCom extends Component {
constructor(props) {
super(props);
}
handleClick = () => {
// withRouter 使组件获得了 location history match 三个属性
this.props.history.push({
pathname: '/home',
search: '?name=testname'
});
}
render() {
console.log(this.props);
return (
<div className="test-container">
this is Test Page
<button onClick={this.handleClick}>点击回到 home page</button>
</div>
);
}
}
// 函数定义组件,不能访问 this
import React from 'react';
import MemberItems from './MemberItems';
/*
纯展示型组件可以使用函数式组件,传入的第一个参数是 props ,在这里不能访问 this
*/
const MemberList = ({memberList}) => (
<div className="member-list-wrap">
<ul className="member-list">
<MemberItems memberList={memberList}/>
</ul>
</div>
);
export default MemberList;
2. 不用于视觉输出的东西的存储
如果需要存储不用于视觉输出的东西,可以直接添加到类中,而不是存储在状态中
如果你不在 render() 中使用某些东西,它就不应该在状态 state 中
export default class TestCom extends Component {
constructor(props) {
super(props);
this.state = {count: 0};
}
tick = () => {
this.setState({count: this.state.count + 1});
}
// 定时器就是一个不需要视觉输出的,但是却需要绑定在组件上,这个时候直接挂在到 this 上即可
componentDidMount() {
this.countTimer = setInterval(() => {
this.tick();
}, 1000)
}
componentWillUnmount() {
clearInterval(this.countTimer);
}
render() {
return (
<div className="test-container">{this.state.count}</div>
);
}
}
3. this.setState() 可以接收一个函数来处理异步的数据更新
this.setState({data: ''})
函数可以传入一个对象用于更新 state ,但是如果有多个 setState() 调用,React 会将多个setState() 调用合并成一个调用来提高性能,也就是说更新可能是异步的
handleClick = () => {
// 只有最后一个 setState() 更新的 state 有效
this.setState({count: this.state.count + 10});
this.setState({count: this.state.count * 2});
this.setState({count: this.state.count + 22});
}
this.setState((prevState, props) => {})
允许传入一个函数作为参数,处理异步的更新,函数接收 prevState (先前的状态) 和 props (此次更新应用时的 props) 两个参数,函数需要返回一个计算后的数据对象
// 三个更新会依次执行,只触发一次 update ,也就是重新 render 一次
handleClick = () => {
this.setState((prevState) => ({count: prevState.count + 10}));
this.setState((prevState) => ({count: prevState.count * 2}));
this.setState((prevState) => ({count: prevState.count + 22}));
}
4. 事件处理函数写法
- 使用 es6 箭头函数代替
.bind(this)
- 需要传递参数的事件处理函数 还是使用
.bind(this, props)
绑定在组件上
这里只是使用习惯
export default class TestCom extends Component {
constructor(props) {
super(props);
}
// 使用箭头函数,自动绑定 this
handleClick = () => {
console.log(this);
}
render() {
return (
<button onClick={this.handleClick}>点击</button>
);
}
}
export default class TestCom extends Component {
constructor(props) {
super(props);
}
handleClick (test, e) {
console.log(this);
}
render() {
// 如果传递参数,还是在这里通过 bind ,函数处理默认接收最后一个参数是 event 对象
return (
<button onClick={this.handleClick.bind(this, 'test')}>点击</button>
);
}
}
5. 数字 0 会被渲染
false、null、undefined 和 true 都是有效的子代,但它们不会直接被渲染,但是 JavaScript 中的一些 “falsy” 值(比如数字0),它们依然会被渲染,因此如果不想渲染要确保 && 前面的表达式始终为布尔值
// count = 0 时渲染为 0
{count && <p>has count num: {count}</p>}
// count = 0 时不渲染
{!!count && <p>has count num: {count}</p>}
6. Refs 使用
1. 上一个版本的 refs 使用方法,还是有效的
// 子组件
export default class TestForm extends PureComponent {
constructor(props) {
super(props);
this.state = {name: 'test name'}
}
getName = () => {
return this.state.name;
}
render() {
return <div></div>;
}
}
// 父组件
export default class TestCom extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log(this.refs.test);
// 这里还是能够通过 this.refs 获取对应的组件并且使用组件的方法
const name = this.refs.test.getName();
console.log(name);
}
render() {
const {count} = this.state;
return (
<div className="test-container">
<TestChild ref="test"/>
</div>
);
}
}
2. refs 新的使用方法(官方推荐方法)
ref 属性接收一个回调函数,它在组件被加载或卸载时立刻执行,这个回调函数接收了底层的 DOM 元素作为参数,可以将其存储到当前实例上(this 上)
不仅可以在 html 标签上,也可以将其使用在类组件上,且仅限 class 声明的类组件
// 子组件
export default class TestForm extends PureComponent {
constructor(props) {
super(props);
this.state = {name: 'test name'}
}
getName = () => {
return this.state.name;
}
render() {
return <div></div>;
}
}
// 父组件
export default class TestCom extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log(this.TestForm);
// 直接从实例上获取
const name = this.TestForm.getName();
console.log(name);
}
render() {
const {count} = this.state;
// ref 值为回调函数,将组件实例挂在到 this 上
return (
<div className="test-container">
<TestForm ref={TestForm => this.TestForm = TestForm}/>
</div>
);
}
}
7. 避免重复渲染
- 通过 shouldComponentUpdate 生命周期函数提升速度
- 通过插件
pure-render-decorator
插件实现 - 通过继承 React 提供的
PureComponent
组件实现
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return (
<button> click </button>
);
}
}
8. render 方法 可以返回一个数组
react 16 开始 render 可以返回一个数组
export default class MemberItems extends PureComponent {
constructor(props) {
super(props);
}
render() {
const memberList = this.props.memberList;
// map 方法的返回值是一个数组
return memberList.map((item, i) => {
return (
<li key={i}>
<p>name:<span>{item.name}</span></p>
<p>tel:<span>{item.tel}</span></p>
</li>
);
});
}
}
9. Fragments 聚合子元素
Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点, Fragments 看起来像空的 JSX 标签
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
// 更清晰的写法 用来添加 key
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
10. 使用 Portals 将子节点渲染到父组件以外的 DOM 节点
import {PureComponent} from 'react';
import ReactDom from 'react-dom';
export default class TestForm extends PureComponent {
constructor(props) {
super(props);
this.el = document.querySelector('body');
}
// 直接插入到 body 中
render() {
return ReactDom.createPortal(
this.props.children,
this.el
)
}
}
11. 错误边界
部分 UI 的异常不应该破坏了整个应用,这是使用错误边界的初衷,错误边界可以捕获其子组件树 JavaScript 异常,记录错误并展示一个回退的 UI 的 React 组件,而不是整个组件树的异常。一下是官网示例
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// Display fallback UI
this.setState({ hasError: true });
// You can also log the error to an error reporting service
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// 组件中使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>