nextjs and cheetsheat

https://github.com/CroMarmot/ReactDemo/commits/master

Next

工具

功能 doc
Head https://github.com/CroMarmot/ReactDemo/commit/00dc0b74d4363e3be39433647a63c3fbe8a96d66
Script https://github.com/CroMarmot/ReactDemo/commit/0cb1c2aac95b276a689bb49e31f7a147d78c159e
静态生成,Link,route,404 getStaticPaths getStaticProps
global css, module css commit
static image https://github.com/CroMarmot/ReactDemo/commit/0a826c74cef03573ced89c2eb36c70a763401fba

Vercel 部署

https://vercel.com/new

Init

https://nextjs.org/learn/foundations/

1
npx create-next-app@latest --ts

React

React use Babel to compile JSX

严格模式

https://zh-hans.reactjs.org/docs/strict-mode.html

识别不安全的生命周期

关于使用过时字符串 ref API 的警告

关于使用废弃的 findDOMNode 方法的警告

检测意外的副作用

检测过时的 context API

确保可复用的状态

1
2
3
4
5
6
7
8
9
10
import React, { StrictMode } from 'react';

import ReactDOM from 'react-dom';

ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
document.getElementById('root')
);

状态

Class组件

1
2
3
4
5
class ExampleClassComponent extends React.Component {
render() {
return <h1>Example Class Component</h1>;
}
}

函数式组件

React 组件名必须要首字母大写

1
2
3
function ExampleFunctionalComponent() {
return <h1>Example Class Component</h1>;
}
1
2
3
const ExampleFunctionalComponent = () => {
return <h1>Example Class Component</h1>;
}
1
2
3
4
5
6
7
import { memo } from 'React';

const ExamplePureComponent = memo({ portal }) => {
return (
<h1>Welcome to {portal}!</h1>
);
}

传参

1
2
3
4
5
6
7
class PropsExample extends React.Component {
render() {
const { portalName } = this.props;

return <h1>Welcome to {portalName}</h1>;
}
}
1
2
3
const PropsExample = (props) => {
return <h1>Welcome to {props.portalName}</h1>;
}
1
2
3
const PropsExample = ({ portalName }) => {
return <h1>Welcome to {portalName}</h1>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import PropTypes from 'prop-types';

const App = ({ name, age, rightHanded }) => {
return (
<div>
<p>Name: {name.firstName}</p>
<p>Surname: {name.lastName}</p>
<p>Age: {age}</p>
<p>Dominant hand: {rightHanded ? 'right' : 'left'}</p>
</div>
);
};

App.propTypes = {
name: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string
}),
age: PropTypes.number,
rightHanded: PropTypes.oneOf([true, false]),
};

export default App;

子组件 slot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const App = () => {
return (
<>
<ParentComponent>
<ul>
<li>This</li>
<li>is</li>
<li>children</li>
<li>element</li>
</ul>
</ParentComponent>
</>
);
};

const ParentComponent = ({ children }) => {
return (
<div>
{children}
</div>
);
};

状态 useState

1
2
3
4
5
6
7
8
9
10
11
12
13
import { useState } from 'react';
import Counter from './User';

function App() {
const [year, setYear] = useState(1237)
return (
<div>
<Counter
warning={`Current year is ${year}`}
/>
</div>
);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UserAccount extends React.Component {
constructor(props) {
super(props);
this.state = {
username: 'mike123',
userAge: 21
};
}

render() {
return (
<div>
<h2>User account</h2>
<ul>
<li>Username: {this.state.username}</li>
<li>Age: {this.state.userAge}</li>
</ul>
</div>
);
}
}

跨层级变量 useContext

提供

1
2
3
4
5
6
7
8
9
10
import React, { createContext } from 'react';
...
export const <context-name> = createContext();

ReactDOM.render(
<<context-name>.Provider value={<context-value>}>
<App />
</<context-name>.Provider>,
document.getElementById('root')
);

使用

1
2
3
4
5
6
7
import { useContext } from 'react';

import { <context-name> } from './index';

...
const <context-variable> = useContext(<context-name>);
console.log(<context-variable>);

useEffect

请记住,Effects通常用于 “走出 “你的React代码并与一些外部系统同步。这包括浏览器API、第三方小工具、网络等等。如果你的Effect只是根据其他状态来调整一些状态, 你并不需要useEffect (https://beta.reactjs.org/learn/you-might-not-need-an-effect)

https://www.reddit.com/r/reactjs/comments/v5ypd9/goodbye_useeffect_reactathon_2022/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import axios from 'axios';
import { useEffect } from 'react';

const App = () => {
useEffect(() => {
const fetchData = async () => {
...
};

fetchData()
}, []);

return <div></div>;
};

We’re calling the fetchData function that’s responsible for fetching data from the server. We execute it on every re-render because the dependency array is empty.

DOM对象 useRef

非常量不需要渲染的引用, 或者对一个对象实体

a React Hook that lets you reference a value that’s not needed for rendering.

和 useState 区别

  • 返回的是 {current}
  • 变化时不触发re-render
  • 在rendering执行外可以 修改, state 是不可修改的
  • rendering时 不应该读 current(所以你不应该在渲染中用{ref.current}), state 随时有snapshot可读

使用场景

  • 存 timeout id
  • 操作dom元素
  • 存其它对jsx 不必要的objects
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
import { useState, useRef } from 'react';

export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);

function handleStart() {
setStartTime(Date.now());
setNow(Date.now());

clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}

function handleStop() {
clearInterval(intervalRef.current);
}

let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}

return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useState, useRef, useEffect } from 'react';

function VideoPlayer({ src, isPlaying }) {
const ref = useRef(null);

useEffect(() => {
if (isPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
});

return <video ref={ref} src={src} loop playsInline />;
}

自定义 state 的处理逻辑 useReducer

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
import { useReducer } from "react";
import ReactDOM from "react-dom/client";

const initialTodos = [
{
id: 1,
title: "Todo 1",
complete: false,
},
{
id: 2,
title: "Todo 2",
complete: false,
},
];

const reducer = (state, action) => {
switch (action.type) {
case "COMPLETE":
return state.map((todo) => {
if (todo.id === action.id) {
return { ...todo, complete: !todo.complete };
} else {
return todo;
}
});
default:
return state;
}
};

function Todos() {
const [todos, dispatch] = useReducer(reducer, initialTodos);

const handleComplete = (todo) => {
dispatch({ type: "COMPLETE", id: todo.id });
};

return (
<>
{todos.map((todo) => (
<div key={todo.id}>
<label>
<input
type="checkbox"
checked={todo.complete}
onChange={() => handleComplete(todo)}
/>
{todo.title}
</label>
</div>
))}
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Todos />);

复用 useMemo , useCallback

包裹自己的话, 用React.memo的高阶函数包裹

例如 页面有输入框, 也有和输入框无关的 函数计算, 可以让输入框变化时, 那些无关的计算不会被调用

1
2
3
4
5
6
7
8
9
10
11
import { useState, useMemo } from "react";

const calculation = useMemo(() => expensiveCalculation(count), [count]);

const expensiveCalculation = (num) => {
console.log("Calculating...");
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
};

这种情况 count是 数字不会影响

而如果 props中有传回调函数, 而组件自身会变化, 那么回调函数地址会变化, useMemo无法做到希望的”入参不变不重渲染”

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 { useState, useCallback } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);

const increment = () => {
setCount((c) => c + 1);
};
const addTodo = useCallback(() => { // todos 不变时, count变化, 不会重新创建addTodo
setTodos((t) => [...t, "New Todo"]);
}, [todos]);

return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { memo } from "react";

const Todos = ({ todos, addTodo }) => {
console.log("child render");
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</>
);
};

export default memo(Todos);

Redux

1
yarn add @reduxjs/toolkit react-redux

Store

1
2
3
4
5
6
7
8
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counter/counterSlice";

export default configureStore({
reducer: {
counter: counterReducer,
},
});

Slice

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
import { createSlice } from "@reduxjs/toolkit";

export const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

export default counterSlice.reducer;

Page

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
import { Provider } from 'react-redux'
import store from './store'
import { useSelector, useDispatch } from "react-redux";
import { decrement, increment } from "./features/counter/counterSlice";

export function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();

return (
<div>
<div>
<button
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span>{count}</span>
<button
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
Decrement
</button>
</div>
</div>
);
}

export default function ReduxDemo() {
return (
<Provider store={store}>
<Counter />
<Counter />
</Provider>
)
}

迷思

回调

看起来 React单向数据流似乎很简洁, 但实际上 本身就有大量回调存在的可能性, 而这种回调在state变化状态下 竟然会变, 否则要自己加useCallback去管理, 感觉好蠢, 并且这样的设计下, 使用者可能有不需要的”回调”, 又变成在内部要判断未传递时的情况, 真的越想越蠢

children/slot 可能破坏 抽离等价

antd的Select, 下面的可以

1
2
3
<Select>
{items.map(item => <Option key={item} value={item}>{item}</Option>)}
</Select>

而下面的不行(看起来有所谓的抽离等价, 实际上配上Select具体内部实现又没有了抽离等价, 问题属于slot/children的问题)

1
2
3
4
5
const StringOption = ({value}) => <Option key={value} value={value}>{value}</Option>;

<Select>
{items.map(item => <StringOption value={item} />)}
</Select>

for的key 大多数情况是浪费的??

vue 同