React-Cheatsheet
文章目录

React: Cheatsheet

DOM 相关

在 body 中插入 JS 并运行

一些外部插件可能经常用到这个方法, 比如 百度统计 , Disqus .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
componentDidMount() {
const s = document.createElement('script')
const username = window.disqusProxy.username;
s.src = `https://${username}.disqus.com/embed.js`
s.async = true
s.setAttribute('data-timestamp', String(+new Date()))
s.onload = () => {
this.setState({
disqusLoaded: true
})
console.log('Native Disqus.')
}
s.onerror = () => {
this.setState({
disqusLoaded: false
})
console.log('Proxy Disqus')
}
document.body.appendChild(s);
}

生命周期

useEffect 里面使用 async 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getTempForm = async () => {
const {data, loading, error} = useAccountTempFormQuery({
variables: {
tempId: 'sdf'
}
})
console.log(data);
}

useEffect(() => {
// 或者定义到这里面
// const getTempForm = async () => { ... }
getTempForm()
}, [tempId])

或者直接在里面调用 Hooks

1
2
3
4
5
6
7
8
useEffect(() => {
const {data, loading, error} = useAccountTempFormQuery({
variables: {
tempId: 'sdf'
}
})
console.log(data);
}, [tempId])

JSX 循环

  1. 使用数组的 .map()
  2. 对于 Object 使用 Object.keys() 等方法创造一个数组,使用数组的 .map()
  3. 对于普通循环也是创造一个数组: [...Array(numberOfEntryFields)] 然后使用数组的 .map()

React CDN 加载

一个完整的 React 纯客户端的实现:

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
<html>

<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<style>
body {
background-color: #1d2126;
color: white;
}
</style>
</head>

<body>
<div id="root"></div>
<script type="text/babel">

ReactDOM.render(
<h1>test</h1>,
document.getElementById('root')
);

</script>
</body>

</html>

事件相关

事件绑定

Bind 方法

1
<button onClick={this.handleEdit.bind(this, param)}> 编辑 </button>

这个方法的缺陷是对于每一个 Elem 都会生成一个实例方法

所以在对应的控件数量很多的情况下不推荐使用

另外使用箭头函数与上面的方法类似:

1
<button onClick={(event) => this.handleEdit(event)}> 编辑 </button>

传递一个额外参数 Pass an extra parameter in onClick

1
2
3
4
<Button 
variant="primary"
onClick={(event)=>props.showModal(event, '234')}
>Login</Button>

首先上方第 1 个参数其实是 event , 然后对于第 2 个参数就可以自由传递

下方就是一个使用 redux 来 dispatch 的事件的例子

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
const showModal = (event, text) => {
return dispatch => {
dispatch({
type: SHOWMODAL,
text
})
}
}

const mapDispatchToProps = dispatch =>
bindActionCreators(
{
showModal,
},
dispatch
)

或者

const mapDispatchToProps = dispatch =>
bindActionCreators(
{
showModal: (event, text) => {
return dispatch => {
dispatch({
type: SHOWMODAL,
text
})
}
}
},
dispatch
)

构造函数内声明

这个是之前常用的,并且是官方推荐的方法

1
2
3
4
5
6
constructor(props){
super(props);

this.handleEdit = this.handleEdit.bind(this);

}

Do not refresh while submit form

1
2
3
<form onSubmit={event => props.searchItem(event)}>
...
</form>

其实这边的处理方法和老的方法一样

1
2
3
4
export const searchItem = event => (dispatch) => {
event.preventDefault();
...
}

当然另一种方法就是将底下的按钮改成 type="button"

但是这样子就并不是一个完整的 form 了

片段: 发送短信的按钮设计

主要是这么几个需求:

  1. 点击按钮之后开始倒计时 60 秒,同时按钮被禁用
  2. 倒计时到 15 秒左右按钮重新使用

难点:

  1. 定时器的设计
  2. 如何清除这个定时器
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends React.Component {
constructor(props) {
super(props);

this.state = {
time: 0,
msg: "",
disabled: false,
timer: undefined,
};
}

/* 其实发送函数的结构可以写的非常简单 */
sendSMS = () => {

/* 任何时候重新点击首先清除定时器 */
clearInterval(this.state.timer);

this.setState({
disabled: true,
time: 30,

/*
下面这一段我们会将生成的定时器放到 state 里面,
因为下一次清除定时器的时候需要用到这个值
*/
timer: setInterval(() => {
/* 再然后我们下方写定时器的计时时间控制 */
if (this.state.time === 0) {
clearInterval(this.state.timer);
return;
} else if (this.state.time === 20) {
this.setState({
disabled: false
});
}
this.setState({
time: this.state.time - 1
});
}, 1000)
});
};

render() {
return [
<div>
{this.state.time === 0
? ""
: "Resend after" + this.state.time + "sec"}{" "}
</div>,
<button
onClick={this.sendSMS}
disabled={this.state.disabled === true ? true : false}
>
Send SMS
</button>
];
}
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React: Form 里面判断 checkbox

1
2
3
handleStockedChange(e){
var value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
}

Mobx Integration

Typescript Start Template

store.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {
observable
} from 'mobx';

/* store + reducer */
class AppState {
@observable timer = 0

constructor() {
// setInterval(() => {
// this.timer += 1;
// }, 1000);
}

resetTimer() {
this.timer = 0;
}
}

export default AppState;

index.tsx : 在这个文件里面要引用上面那个文件 store.ts

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
35
36
37
38
39
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import {
observer
} from 'mobx-react';
import AppState from './store';

@observer
class TimerView extends React.Component < {
appState: AppState
}, {}> {
render() {
return ( <
div >
<
button onClick = {
this.onReset
} >
Seconds passed: {
' '
} {
this.props.appState.timer
} <
/button> < /
div >
);
}

onReset = () => {
this.props.appState.resetTimer();
}
}

const store = new AppState();

ReactDOM.render( < TimerView appState = {
store
}
/>, document.getElementById('root'));

Hints:

  • View Class 以及 Store Class 里面都添加了 Decorators
    • Typescript 里面如果要使用 Decorators 那么就需要到 tsconfig.json 里面设置一下 "experimentalDecorators": true
  • store 保存在一个单独的文件里面, 并且是设置为一个 class
    • 引用了这个 class 之后,一定要记得初始化一遍: const store = new AppState();
  • 并且要将初始化之后的实例传给 props: TimerView appState={store} /
  • 对于初始化的代码完全可以放到 store 的 constructor()
  • 上面这种模式是很简单的,对于一个 Component 进行状态管理的模板。如果希望进行全局状态管理, 那么需要使用 @inject

Use inject to get global store

index.tsx :

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
35
36
37
38
39
40
41
import React from 'react';
import ReactDOM from 'react-dom';
import './styles/App.scss';
import {
observer,
Provider,
inject
} from 'mobx-react';
import {
observable
} from 'mobx';
import App from './containers/App';
import * as serviceWorker from './serviceWorker';

/* store + reducer */
class AppState {
@observable timer = 0

constructor() {
setInterval(() => {
this.timer += 1;
}, 1000);
}

resetTimer() {
this.timer = 0;
}
}

const s = new AppState();

ReactDOM.render( <
Provider store = {
s
} >
<
App / >
<
/Provider>,
document.getElementById('root'),
);

Home.tsx :

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React from 'react';
import Button from 'antd/es/button';
import {
observer,
inject
} from 'mobx-react';

interface MyComponentProps {}

interface IRecipeProps {
store: {
resetTimer: () => void,
timer: Number,
};
}

@inject('store')
@observer
class Home extends React.Component <MyComponentProps> {
get injected() {
return this.props as IRecipeProps;
}

onReset = () => {
const {
store
} = this.injected;
store.resetTimer();
}

render() {
const {
store
} = this.injected;
return ( <
div >
<
Button type = "primary"
onClick = {
this.onReset
} >
Seconds passed: {
store.timer
} <
/Button> < /
div >
);
}
}

export default Home;

Hints:

  1. 使用 Provider 将根节点包裹, 如此就可以在所有的子节点里面使用 @inject

  2. 根据在根节点传递给 Provider 的 Attr, 可以在 inject 之后的子节点里面通过 this.props 索引到

    • 这里会存在一些麻烦:

      • Props 一般会使用一个接口进行规范化, 而 props 现在有两个输入点,一个是 inject 传进来, 一个是通过父控件通过正常 attr 传进来
    • 解决方法 (也是 2020 年 2 月,这个方法还可以使用)

      • 创建一个 get 方法, 这个方法将接收到的所有 props 转化成 injected 的 interface
      • 调用的时候,对于 props 里面的数据应该直接使用 this.props
      • 对于 inject 里面的数据就要通过 this.injected 来进行调用。
      • 通过这个方式可以规范化两种数据。

Reference: https://github.com/mobxjs/mobx-react/issues/256#issuecomment-335903780

Ref

父组件之中操作子组件 - Ref 的简单使用

仅仅一层的 ref 传递:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function TextInputWithFocusButton() {
/* 创建一个 ref Object */
const inputEl = useRef(null);

/* 将 ref 传递给子组之后就可以在这个地方控制子组件 */
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};

return (
<>
{/* 但是要将 ref 传给对应的子组件才会生效 (这个操作就类似于绑定) */}
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

多层的 Ref 传递 + FC, 需要使用 forwardRef 方法:

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
import React, { useRef } from "react";
import "./styles.css";

/* 其中主要的区别就是要通过 forwardRef 将 ref 传递给子控件 */
const FancyButton = React.forwardRef((props, ref) => {
return <button ref={ref} className="FancyButton">
{props.children}
</button>
});

const TextInputWithFocusButton = () => {
const inputEl = useRef(null);

const onButtonClick = () => {
/* 然后这个方法这里就可以让焦点聚焦到 FancyButton 里面那个 button 的 DOM 上 */
inputEl.current.focus();
};

return (
<>
<FancyButton ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
};
export default TextInputWithFocusButton;

Troubleshooting

React&Error: Unterminated JSX contents

这个是写了两个 elem 或者某个 elem 没有闭合

React&Error: ‘react-router’ does not contain an export named ‘browserHistory’

V2 和 V3 版本里面才有这玩意, V4 之后就不再有了

Solution

查看一下官方 API 发现用法有些改变

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

const BasicExample = () => ( <Router>
<div>
<ul>
<li><Link to = "/"> Home </Link></li>
<li><Link to = "/about"> About </Link></li>
<li><Link to = "/topics"> Topics </Link></li>
</ul>
<hr/>
<Route exact path = "/" component = {Home}/>
<Route path = "/about" component = { About} />
<Route path = "/topics" component = { Topics} />
</div>
</Router>
)

React&Error: Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null

八成就是 return 后面换行了, JS 会自动加分号所以返回了 undefined# React&Error: Cannot call a class as a function.md

class component 看起来不能通过函数的方式进行调用
那么就改成 tag 模式调用就行:

1
2
3
4
5
6
7
8
9
render() {
return (
<div>
<WelcomeLList nameList = {nameList}/>
// {WelcomeList(this.props.nameList)}
// ↑↑ DO NOT use this way if you created class component
</div>
);
}

React Function Components cannot have refs.

使用 Ref 可以获取到对应的 DOM

1
const node = this.myRef.current;

但是要注意 function components 不能直接使用 ref, 因为 FC 并不含有 instance

但是如果不得不使用的话:

  1. 改成 class component
  2. 换用 forwardRef
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
// Does work!
import React, {
useState,
useEffect,
useRef,
forwardRef,
} from 'react';
const App = () => {
const [greeting, setGreeting] = useState('Hello React!');
const handleChange = event => setGreeting(event.target.value);
const ref = useRef();
useEffect(() => ref.current.focus(), []);
return (
<div>
<h1>{greeting}</h1>
<Input value={greeting} handleChange={handleChange} ref={ref} />
</div>
);
};

// 第一个参数是 props, 第二个参数是 ref
const Input = forwardRef(({value, handleChange}, ref) => (
<input
type="text"
value={value}
onChange={handleChange}
// 下方直接赋值给 ref, 就像中转一下
ref={ref}
/>
));
export default App;