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(() => { 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 循环
使用数组的 .map()
对于 Object 使用 Object.keys()
等方法创造一个数组,使用数组的 .map()
对于普通循环也是创造一个数组: [...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 > <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>
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); }
1 2 3 <form onSubmit={event => props.searchItem(event)}> ... </form>
其实这边的处理方法和老的方法一样
1 2 3 4 export const searchItem = event => (dispatch ) => { event.preventDefault(); ... }
当然另一种方法就是将底下的按钮改成 type="button"
但是这样子就并不是一个完整的 form 了
片段: 发送短信的按钮设计
主要是这么几个需求:
点击按钮之后开始倒计时 60 秒,同时按钮被禁用
倒计时到 15 秒左右按钮重新使用
难点:
定时器的设计
如何清除这个定时器
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 , 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);
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' ; class AppState { @observable timer = 0 constructor ( ) { } 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' ;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:
使用 Provider 将根节点包裹, 如此就可以在所有的子节点里面使用 @inject
根据在根节点传递给 Provider 的 Attr, 可以在 inject 之后的子节点里面通过 this.props 索引到
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 ( ) { const inputEl = useRef(null ); const onButtonClick = () => { inputEl.current.focus(); }; return ( <> {} <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" ;const FancyButton = React.forwardRef((props, ref ) => { return <button ref ={ref} className ="FancyButton" > {props.children} </button > }); const TextInputWithFocusButton = () => { const inputEl = useRef(null ); const onButtonClick = () => { 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}/> </div> ); }
React Function Components cannot have refs.
使用 Ref 可以获取到对应的 DOM
1 const node = this .myRef.current;
但是要注意 function components 不能直接使用 ref, 因为 FC 并不含有 instance
但是如果不得不使用的话:
改成 class component
换用 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 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> ); }; const Input = forwardRef(({value, handleChange}, ref ) => ( <input type="text" value={value} onChange={handleChange} ref={ref} /> )); export default App;