React 必知必会

努力更新中…

常见绑定事件的操作

查看代码

结论:应尽量避免 render 中使用箭头函数或 bind 绑定!

bind 的形式

1
2
3
handleClick() {
this.setState((prevState, props) => ({num: prevState.num+1}));
}
1
<Button type="primary" onClick={this.handleClick.bind(this)}>点击</Button>

定义实例方法时采用箭头函数的形式,推荐!

1
2
3
handleClick = () => {
this.setState((prevState, props) => ({num: prevState.num+1}));
}
1
<Button type="primary" onClick={this.handleClick}>点击</Button>

绑定事件时采用箭头函数的形式

1
2
3
handleClick() {
this.setState((prevState, props) => ({num: prevState.num+1}));
}
1
<Button type="primary" onClick={() => this.handleClick()}>点击</Button>

实例方法和绑定事件都采用箭头函数的形式

1
2
3
handleClick = () => {
this.setState((prevState, props) => ({num: prevState.num+1}));
}
1
2
// 方便值的传递
<Button type="primary" onClick={() => this.handleClick()}>点击</Button>

constructor 中就扭正 this 的指向

1
2
3
4
5
6
7
constructor(props) {
super(props);
this.state = {
num: 0
};
this.handleClick = this.handleClick.bind(this);
}
1
2
3
handleClick() {
this.setState((prevState, props) => ({num: prevState.num+1}));
}
1
<Button type="primary" onClick={this.handleClick}>点击</Button>

Error Boundaries

部分 UI 的 JavaScript 错误不应该导致整个应用崩溃,为了解决这个问题,React 16 引入了一个新的概念 —— 错误边界。查看代码

1
2
3
4
5
6
7
8
9
export default class App extends Component {
render() {
return (
<ErrorBoundary>
<Test/>
</ErrorBoundary>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
error: false
};
}
componentDidCatch(error, info) {
this.setState({
error,
info
});
}
render() {
if(this.state.error) {
return (
<h1>出错啦!</h1>
);
}
return this.props.children;
}
}

Portals

查看代码

可以将子节点渲染到存在于父组件以外的 DOM 节点,常用于弹框、对话框等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Modal extends Component {
constructor(props) {
super(props);
this.container = document.createElement('div');
document.body.appendChild(this.container);
}
componentWillUnmount() {
document.body.removeChild(this.container);
}
render() {
return ReactDOM.createPortal(
<div className="modal">
<span className="close" onClick={this.props.onClose}>&times;</span>
<div className="content">
{this.props.children}
</div>
</div>,
this.container
)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export default class Test extends Component {
constructor(props) {
super(props);
this.state = {
showModal: true
};
}
closeModal = () => {
this.setState({
showModal: false
});
}
render() {
return (
<>
<h2>标题</h2>
{
this.state.showModal && (
<Modal onClose={this.closeModal}>
Modal Dialog
</Modal>
)
}
</>
);
}
}

Fragments

查看代码

Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点

1
2
3
4
5
6
7
8
9
10
11
export default class Fragment extends Component {
render() {
return (
<React.Fragment>
<p>1</p>
<p>2</p>
<p>3</p>
</React.Fragment>
);
}
}

原理实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
// const Fragment = ({children}) => children;
const Fragment = (props) => props.children;
export default class Fragment extends Component {
render() {
return (
<Fragment>
<p>1</p>
<p>2</p>
<p>3</p>
</Fragment>
);
}
}

Ref 获取元素/组件

需求:打开页面使 Input 获取焦点查看代码

通过 this.refs.xxx

1
<input type="text" ref="inputRef"/>
1
2
3
4
// 注意是 componentDidMount
componentDidMount() {
this.refs.inputRef.focus();
}

通过 ref 接受一个函数进行处理

1
<input type="text" ref={ele => ele.focus()}/>

或者

1
2
3
4
componentDidMount() {
this.inputRef.focus();
}
<input type="text" ref={ele => this.inputRef=ele}/>

利用 React.createRef()

1
2
3
4
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
1
2
3
componentDidMount() {
this.inputRef.current.focus();
}
1
<input type="text" ref={this.inputRef}/>

如何自取子组件(函数)中的 DOM 节点?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, {Component} from 'react';

export default class AboutRef extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleClick = () => {
// this.inputRef.current.refs.test.style.backgroundColor = 'red';
this.inputRef.current.style.backgroundColor = 'red';
}
render() {
return (
<div>
<Test ref={this.inputRef}/>
<button onClick={this.handleClick}>改变组件</button>
</div>
);
}
}

/* class Test extends Component {
render() {
return (
<div ref="test">hello world</div>
);
}
} */

const Test = React.forwardRef((props, ref) => {
return (
<div ref={ref}>hello world</div>
);
});

新增生命周期

v16.3 新增,v16.4 再次更新了 getDerivedStateFromProps,查看代码

1
2
3
4
5
static getDerivedStateFromProps(nextProps, prevState) {
// 将传入的 props 映射到 state 上面,props 更新 和 setState 时都会触发
const { number } = nextProps;
return number % 2 === 0 ? { num: number + 1 } : { num: number + 3 };
}
1
2
3
4
5
6
7
8
9
getSnapshotBeforeUpdate() {
// 发生于 render 之后,但并没有渲染完毕,可以从 DOM 中捕获一些信息(例如滚动之前的高度)
// 返回值会作为 componentDidUpdate 的第三个参数
console.log(2);
return this.ulRef.current.offsetHeight
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log(snapshot);
}

跨组件传值

查看代码

旧版 Context API

1
2
3
4
5
// Step1: 父
static childContextTypes={
color: PropTypes.string,
changeColor:PropTypes.func
}
1
2
3
4
5
6
7
8
9
// Step2: 父
getChildContext() {
return {
color: this.state.color,
changeColor:(color)=>{
this.setState({color})
}
}
}
1
2
3
4
5
// step3: 子孙
static contextTypes={
color: PropTypes.string,
changeColor:PropTypes.func
}
1
2
// step4: 子孙
<button onClick={() => this.context.changeColor('pink')}>改变孙子的颜色</button>

新版 Context API

React 16.3

1
2
// Step1: 全局
const ThemeContent = React.createContext();
1
2
3
4
5
6
7
8
9
10
11
12
// Step2: 父
const ctx = {
color: this.state.color,
changeColor: this.changeColor
};
<ThemeContent.Provider value={ctx}>
<div style={{ border: '3px solid red' }}>
<h1>父</h1>
<Large1 />
<Large2 />
</div>
</ThemeContent.Provider>
1
2
3
4
5
6
7
8
9
10
11
// Step3: 子孙
<ThemeContent.Consumer>
{
value => (
<div style={{ border: '3px solid red', margin: 10, color: value.color }}>
<h4>孙子2</h4>
<button onClick={() => value.changeColor('pink')}>改变孙子的颜色</button>
</div>
)
}
</ThemeContent.Consumer>

上面的 Step3 也可以改写如下:

React16.6 提供的 API

1
static contextType = ThemeContent;
1
<button onClick={() => this.context.changeColor('pink')}>改变孙子的颜色</button>

PureComponent

浅比较新旧 props 和 state,发生变化时才会更新组件,提高效率!查看代码

React.memo()

查看代码

PureComponent 要和 class component 配合使用,而 React.memo() 可以和 function component 一起使用,例如:

1
2
3
4
5
6
7
8
9
function Child(props) {
console.log('res 没有变化时我不会 render ~~');
return (
<div>
结果是:{props.res}
</div>
)
}
Child = React.memo(Child);

React.lazy()

可以实现基于路由的代码分割/懒加载,查看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Suspense, lazy, Component } from 'react';
import { HashRouter as Router, Route, Switch, Link } from 'react-router-dom';

const Home = lazy(() => import('./Home'));
const News = lazy(() => import('./News'));

// render 时需要 return 的内容如下
<Router>
<Suspense fallback={<div>loading...</div>}>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/news">News</Link>
</li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/news" component={News} />
</Switch>
</Suspense>
</Router>

高阶组件及应用

高阶组件是一个函数,能对接收过来的组件进行加工后再返回!查看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 基本操作
const Logger = (Com) => {
return class extends Component {
render() {
return <Com {...this.props}/>;
}
}
}

const Hello = Logger((props) => {
return (
<p>Hello {props.name}</p>
)
});

<Hello name="Ifer"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 案例
const withFetch = url => View => {
return class extends Component {
constructor() {
super();
this.state = {
loading: true,
data: null
};
}
async componentDidMount() {
const res = await fetch(url);
const data = await res.json();
this.setState({
loading: false,
data
});
}
render() {
if(this.state.loading) {
return <div>loading...</div>
} else {
return <View data={this.state.data}></View>
}
}
}
};

Render Props