React-CheatsheetDev, React

React: Cheatsheet

DOM 相关

在 body 中插入 JS 并运行

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

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 方法

  useEffect(() => {
  const fetchData = async () => {
     const data = await getData(1);
     setData(data);
  }

  fetchData();
}, []);

另一种格式:

useEffect(() => {

  (async () => {
     const data = await getData(1);
     setData(data);
  })();

}, []);

JSX 循环

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

React CDN 加载

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

<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 方法

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

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

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

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

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

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

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

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

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

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
  )

构造函数内声明

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

constructor(props){
    super(props);

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

}

Do not refresh while submit form

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

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

export const searchItem = event => (dispatch) => {
    event.preventDefault();
    ...
}

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

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

Snippets: Keyboard Event Listener

很简单, 直接给控件加上即可, 理论上需要提前 focus

onKeyDown={(event) => {
  if (event.key === 'Enter') {
    alert('The sky is your starting point!');
  }
}}



/* Deprecated */
onKeyPress={(event) => {
  if (event.key === 'Enter') {
    alert('The sky is your starting point!');
  }
}}

对于组合按键的情况:

/* Ctrl+Enter */
if ((e.ctrlKey && e.witch == 13) || e.which == 10) {
  triggerSearch();
}

Hooks 的实现

export const useGlobalHotkey = (
  callback: (event: KeyboardEvent) => void
): void => {
  useEffect(() => {
    document.addEventListener("keydown", callback);
    return (): void => {
      document.removeEventListener("keydown", callback);
    };
  }, []);
};



useGlobalHotkey((event) => {
  if ((event.key === "Enter" || event.key === "k") && event.ctrlKey) {
    navigate("/search");
  }
});

Snippets: 使用 useRef 处理计时器

const Index = props => {
    const [nums, setNums] = useState(0);
    
    const goStart = (index) => {
      setStatus(index)
      timer.current = setInterval(() => {
        console.log(timer)
        // 注意此处因为闭包原因使用 callback 来更新值
        setNums(n => {
          return n + 1
        });
      }, 1000)
    }

    const goClear = () => {
      clearInterval(timer);
    }

    return (<div>
      <div onClick={() => { goStart(1) }}>开始</div>
      <div onClick={() => { goClear(1) }}>清除</div>
    </div>)
  }
  export default Index;

Snippets: 发送短信的按钮设计

注意 timer 可以改用 useRef 来进行优化

主要是这么几个需求:

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

难点:

  1. 定时器的设计
  2. 如何清除这个定时器
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

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

Mobx Integration

Typescript Start Template

store.ts

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

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 :

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 :

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 传递:

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 方法:

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 发现用法有些改变

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 模式调用就行:

  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

const node = this.myRef.current;

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

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

  1. 改成 class component
  2. 换用 forwardRef
// 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;
Powered by Remix
|
Designed by szhshp
|
Copyright © szhshp 2022