React

JSX 防止转义<div dangerouslySetInnerHTML={{ __html: this.state.content }} />

没有 React Hook 时的React组件缺点:
  1. 很难重用有状态的逻辑组件,我们通常希望一个函数只做一件事情,但我们的生命周期钩子函数里通常同时做了很多事情。比如我们需要在componentDidMount中发起ajax请求获取数据,绑定一些事件监听等等,有时候我们还需要在componentDidUpdate做一遍同样的事情,有时还需要在componentWillUnmount中解绑事件监听。当项目变复杂后,这一块的代码也变得不那么直观。
  2. 我们用class来创建react组件时,还有一件很麻烦的事情,就是this的指向问题。为了保证this的指向正确,我们要经常写这样的代码:this.handleClick = this.handleClick.bind(this),或者是这样的代码: onClick={(e) => this.handleClick(e)}
  3. 无状态组件的形式更方便复用,可独立测试。然而很多时候,我们用function写了一个无状态组件,后来因为需求变动这个组件必须得有自己的state,我们又得把function改成class
在传统的React中,有2种方法可以在组件中共享状态逻辑
  1. render props
  2. higher-order component

render props 指的是一个值为函数的prop来传递需要动态渲染的nodes或组件

class DataProvider extends React.Component {
  constructor(props) {
    super(props);
    this.state = { target: 'Jammie' };
  }

  render() {
    return <div>{this.props.render(this.state)}</div>;
  }
}

<DataProvider render={data => (
  <Cat target={data.target} />
)}/>

虽然这个模式叫Render Props,但不是说非用一个叫render的props不可,习惯上更常写成下面这种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DataProvider extends React.Component {
constructor(props) {
super(props);
this.state = { target: "Jammie" };
}

render() {
return <div>{this.props.children(this.state)}</div>;
}
}

<DataProvider>
{data => (
<Cat target={data.target} />
)}
</DataProvider>

高阶组件就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件。

1
2
3
4
5
6
7
8
9
10
11
12
const withUser = WrappedComponent => {
const user = sessionStorage.getItem("user");
return props => <WrappedComponent user={user} {...props} />;
};

const UserPage = props => (
<div class="user-container">
<p>My name is {props.user}!</p>
</div>
);

export default withUser(UserPage);

这两种模式缺点是会增加代码的层级关系

Hook

在v16.8以后,还可以使用Hook。

Hooks are a way to reuse stateful logic, not state itself.

Rules of Hooks:

  1. Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
  2. Only call Hooks from React function components or your own custom Hooks. Don’t call Hooks from regular Javascript functions.

How does React know which state corresponds to which useState call?
The answer is that React relies on the order in which Hooks are called. The order of the Hook calls is the same on every render.

Custom Hooks are more of a convention than a feature. If a function’s name starts with “use” and it calls other Hooks, we say it is a custom Hook.

You can write custom Hooks that cover a wide range of use cases like form handling, animation, declarative subscriptions, timers, and probably many more we haven’t considered.

React 目前提供的Hook:
  1. useState
  2. useEffect
  3. useContext
  4. useReducer
  5. useCallback
  6. useMemo
  7. useRef
  8. useImperativeHandle
  9. useLayoutEffect
  10. useDebugValue
1. useState
1
2
const [state, setState] = useState(initialState);  
setState(newState);

unlike this.setState in a class, updating a state variable in useState always replaces it instead of merging if.

2. useEffect

useEffect, adds the ability to perform side effects from a function component. It serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React classes, but unified into a single API.
Network requests, manual DOM mutations, and logging are common examples of effects that don’t require a cleanup.
Data fetching, setting up a subscription, in these cases, it is important to clean up so that we don’t introduce a memory leak.
unlike componentDidMount or componentDidUpdate, effects scheduled with useEffect don’t block the browser from updating the screen.
Although useEffect is deferred until after the browser has painted, it’s guaranteed to fire before any new renders.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { useState, useEffect } from 'react';

function Example(props) {
const [count, setCount] = useState(0);

useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
},[props.friend.id]); //Only re-run the effect if props.friend.id changes.
// [] only run on mount and unmount
// [arg1, arg2, arg3] re-run depends on arg1 || arg2 || arg3 changes

return (
<div>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
3. useContext
1
2
3
4
5
6
7
8
9
10
11
12
# before
const {Provider, Consumer} = React.createContext(defaultValue);
<Provider value={{theme: "dark"}}>
<Component />
</Provider>
<Consumer>
{({theme}) => <Button theme={theme} />}
</Consumer>

# after
const MyContext = React.createContext();
const value = useContext(MyContext);

when the nearest <MyContext.Provider> above the component updates, this Hook will trigger a rerender with the latest context value passed to that MyContext provider.

4. useReducer
1
const [state, dispatch] = useReducer(reducer(state, action) => newState, initialArg, initReducerLazy);

an alternative to useState.
useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.
useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>

        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}
5. useCallback
1
2
3
4
5
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// returns a memoized callback if a or b change
// if no deps is provided, a new value will be computed on every render.

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)

A really useful feature of useCallback is that it returns the same function instance if the depencies don’t change.

Rules : Each function declared within a functional component’s scope must be memoized/cached with useCallback. If it references functions or other variables from the component scope it should list them in its depency list.
import React, { useState } from ‘react’;

// Keeps track of all created functions during the app's life 
const functions: Set<any> = new Set();

const App = () => {
  const [c1, setC1] = useState(0);
  const [c2, setC2] = useState(0);

  // Cache/memoize the functions - do not create new ones on every rerender
  const increment1 = useCallback(() => setC1(c1 + 1), [c1]);
  const increment2 = useCallback(() => setC2(c2 + 1), [c2]);

  // Can depend on [c1, c2] instead, but it would be brittle
    const incrementBoth = useCallback(() => {
        increment1();
        increment2();
    }, [increment1, increment2]); 

  // Register the functions so we can count them
  functions.add(increment1);
  functions.add(increment2);

  return (<div>
    <div> Counter 1 is {c1} </div>
    <div> Counter 2 is {c2} </div>
    <br/>
    <div>
      <button onClick={increment1}>Increment Counter 1</button>
      <button onClick={increment2}>Increment Counter 2</button>
    </div>
    <br/>
    <div> Newly Created Functions: {functions.size - 2} </div>
  </div>)
}
6. useMemo
1
const memoizedValue = useMemo(() => computeExpensiveValue(a,b), [a, b]);

Unlike useCallback, which caches the provided function instance, useMemo invokes the provided function and caches its result.
returns a memoized value. useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.
if no array is provided, a new value will be computed on every render.
You may rely on useMemo as a performance optimization, not as a semantic guarantee.

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
} 
7. useRef
1
const refContainer = useRef(initialValue);

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render.
you can use useRef as instance variables.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}
8. useImperativeHandle
1
useImperativeHandle(ref, createHandle, [deps]);

useImperativeHandle customizes the instance value that is exposed to parent components when using ref.

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

// use
this.ref = React.createRef();
<FancyInput ref={this.ref} />
<button onClick={()=>{this.ref.current.focus()}} />

In this example, a parent component that renderswould be able to call fancyInputRef.current.focus().

9. useLayoutEffect

The signature is identical to useEffect, but it fires synchronously after all DOM mutations.

10. useDebugValue
1
useDebugValue(value, value=>{ return formatValue;});

useDebugValue can be used to display a label for custom hooks in React DevTools.

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
} 

function ProductPage({ productId }) {
  // ✅ Wrap with useCallback to avoid change on every render
  const fetchProduct = useCallback(() => {
    // ... Does something with productId ...
  }, [productId]); // ✅ All useCallback dependencies are specified

  return <ProductDetails fetchProduct={fetchProduct} />;
}

function ProductDetails({ fetchProduct })
  useEffect(() => {
    fetchProduct();
  }, [fetchProduct]); // ✅ All useEffect dependencies are specified
  // ...
}

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1); // ✅ This doesn't depend on `count` variable outside
    }, 1000);
    return () => clearInterval(id);
  }, []); // ✅ Our effect doesn't use any variables in the component scope

  return <h1>{count}</h1>;
}
How to create expensive objects lazily?
// 1. 方法1
function Table(props) {
  // ✅ createRows() is only called once
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}

// 2. 方法2
function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver is created lazily once, this avoids creating an expensive object until it's truly needed for the first time.
  function getObserver() {
    if (ref.current === null) {
      ref.current = new IntersectionObserver(onIntersect);
    }
    return ref.current;
  }

  // When you need it, call getObserver()
  // ...
}

How to avoid passing callbacks down?
in large componet trees, an alternative we recommend is to pass down a dispatch function from useReducer via context

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // Note: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}

// any child in the tree inside TodosApp can use the dispatch function to pass actions up to TodosApp
function DeepChild(props) {
  // If we want to perform an action, we can get dispatch from context.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

useReducer vs Redux

  1. useReducer has two ingredients missing from Redux to make it one and global.

One: First, there is no native feature (yet) which combines all reducers to one ultimate reducer. Redux is offering this feature, but in plain React we would have to implement it ourselves. Only if we were able to combine all state containers form all useReducer hooks, we could speak of one state container.

Global: Second, every useReducer comes with its own dispatch function. There is no native feature (yet) which combines all dispatch functions to one dispatch function. Redux provides one dispatch function that consumes any action dedicated for any reducer function. The dispatch function from useReducer, in contrast, only deals with action that are specified by the reducer function to be consumed.

  1. no middleware with useReducer

模拟整个生命周期中只运行一次的方法

useMemo(() => {
  // execute only once
}, []);

模拟shouldComponentUpdate
const areEqual = (prevProps, nextProps) => {
// 返回结果和shouldComponentUpdate正好相反
// 访问不了state
};
React.memo(Foo, areEqual);

模拟componentDidMount
useEffect(() => {
// 这里在mount时执行一次
}, []);

模拟componentDidUpdate
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
mounted.current = true;
} else {
// 这里只在update是执行
}
});

模拟componentDidUnmount
useEffect(() => {
// 这里在mount时执行一次
return () => {
// 这里在unmount时执行一次
}
}, []);

React Fiber

在React Fiber中,一次更新过程会分成多个分片完成,所以完全有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。
因为一个更新过程可能被打断,所以React Fiber一个更新过程被分为两个阶段: render phase and commit phase.
在 Render phase 中, React Fiber会找出需要更新哪些DOM,这个阶段是可以被打断的, 而到了第二阶段commit phase, 就一鼓作气把DOM更新完,绝不会被打断。
这两个阶段, 分界点是 render 函数。而且, render 函数 也是属于 第一阶段 render phase 的。

render phase:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate

commit phase:
componentDidMount
componentDidUpdate
componentWillUnmount

在现有的React中,每个生命周期函数在一个加载或者更新过程中绝对只会被调用一次;在React Fiber中,不再是这样了,第一阶段中的生命周期函数在一次加载和更新过程中可能会被多次调用!。

纯函数,不能通过this访问到当前组件
static getDerivedStateFromProps(nextProps, prevState) {
  //根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState
}

componentDidCatch

getDerivedStateFromError

render phase 里产生异常的时候, 会调用 getDerivedStateFromError;

在 commit phase 里产生异常的时候, 会调用 componentDidCatch。

严格来说, 其实还有一点区别:

componentDidCatch 是不会在服务器端渲染的时候被调用的 而 getDerivedStateFromError 会。

reconciliation

Suspense

代码分片
//    Clock.js
import React from "react";
import moment from "moment";
const Clock = () => <h1>{moment().format("MMMM Do YYYY, h:mm:ss a")}</h1>;
export default Clock;

// Usage of Clock
const Clock = React.lazy(() => {
  console.log("start importing Clock");
  return import("./Clock");
});

<Suspense fallback={<Loading />}>
  { showClock ? <Clock/> : null}
</Suspense>
异步获取数据
import {unstable_createResource as createResource} from 'react-cache';

const resource = createResource(fetchDataApi);

const Foo = () => {
  const result = resource.read();
  return (
    <div>{result}</div>
  );

// ...

<Suspense>
   <Foo />
</Suskpense>};

React-Router

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
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import { useParams } from "react-router";
function User() {
let { id } = useParams();
return <h2>User {id}</h2>;
}
function App() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/1">User 1</Link>
</li>
<li>
<Link to="/2">User 2</Link>
</li>
</ul>
<Switch>
<Route path="/:id" children={<User />} />
</Switch>
</div>
</Router>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
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
import React from "react";
import ReactDOM from "react-dom";
import { Link, useLocation, BrowserRouter as Router } from "react-router-dom";
function User({ name }) {
return <div>{name}</div>;
}
function useQuery() {
return new URLSearchParams(useLocation().search);
}
function QueryScreen() {
let query = useQuery();
return (
<div>
<div>
<h2>Accounts</h2>
<ul>
<li>
<Link to="/account?name=foo">Foo User</Link>
</li>
<li>
<Link to="/account?name=bar">Bar User</Link>
</li>
<li>
<Link to="/account?name=baz">Baz User</Link>
</li>
</ul>
<User name={query.get("name")} />
</div>
</div>
);
}
function App() {
return (
<Router>
<QueryScreen />
</Router>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

style react component

1. CSS Stylesheet
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 './DottedBox.css';

const DottedBox = () => (
<div className="DottedBox">
<p className="DottedBox_content">Get started with CSS styling</p>
</div>
);

export default DottedBox;

# DottedBox.css
.DottedBox {
margin: 40px;
border: 5px dotted pink;
}

.DottedBox_content {
font-size: 15px;
text-align: center;
}
2. Inline styling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React from 'react';

const divStyle = {
margin: '40px',
border: '5px solid pink'
};
const pStyle = {
fontSize: '15px',
textAlign: 'center'
};

const Box = () => (
<div style={divStyle}>
<p style={pStyle}>Get started with inline style</p>
<p style={{color: 'pink'}}>inline style</p>
</div>
);

export default Box;
3. CSS Modules

To make CSS modules work with Webpack you only have to include the modules mentioned above and add the following loader to your webpack.config.js file

1
2
3
4
5
6
. . .
{
test: /\.css$/,
loader: 'style!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
}
. . .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import styles from './DashedBox.css';

const DashedBox = () => (
<div className={styles.container}>
<p className={styles.content}>Get started with CSS Modules style</p>
</div>
);

export default DashedBox;

# DashedBox.css
:local(.container) {
margin: 40px;
border: 5px dashed pink;
}
:local(.content) {
font-size: 15px;
text-align: center;
}
4. Styled-components

Styled-components is a library for React and React Native that allows you to use component-level styles in your application that are written with a mixture of JavaScript and CSS

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 from 'react';
import styled from 'styled-components';

const Div = styled.div`
margin: 40px;
border: 5px outset pink;
&:hover {
background-color: yellow;
}
`;

const Paragraph = styled.p`
font-size: 15px;
text-align: center;
`;

const OutsetBox = () => (
<Div>
<Paragraph>Get started with styled-components 💅</Paragraph>
</Div>
);

export default OutsetBox;