TUNIVERSE

React笔记

字数统计: 2.3k阅读时长: 11 min
2022/10/22 11

Hooks相关笔记和React项目创建步骤
Hooks部分参考自文档:https://beta.reactjs.org/apis/react

Hooks

memo

原型

memo的对象是组件component,一般是在父组件中为需要用的子组件套上,减少子组件渲染次数。即当父组件有多个子组件时,使用 memo,可以让没有 props 变化的子组件不渲染

1
const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

使用

  1. 当重新渲染某个组件的时候,React 也会渲染它全部的子组件,这就会导致渲染的过程很慢。因此当组件的参数 props 和上一次是一样的时候其实就可以让它跳过,避免重复工作。
    只有当 name 改变时才会重新渲染 Greeting

    1
    2
    3
    const Greeting = memo(function Greeting({ name }) {
    return <h1>Hello, {name}!</h1>;
    });
  2. 当某个组件本身的 state 被改变时,即使他被 memo 了它也会改变。
    比如这个例子,输入名字来打招呼。当 name 名字属性不改变时不会重新渲染 Greeting 组件来打招呼(因为它被 momo 了)但是在它的内部通过 useState 来改变 greeting 的值,即打招呼的方式。每次 setChange 都改变了 state,所以即使名字没有变,也会用新的方式来打招呼。

    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
    export default function MyApp() {
    const [name, setName] = useState('');
    const [address, setAddress] = useState('');
    return (
    <>
    <label>
    Name{': '}
    <input value={name} onChange={e => setName(e.target.value)} />
    </label>
    <label>
    Address{': '}
    <input value={address} onChange={e => setAddress(e.target.value)} />
    </label>
    <Greeting name={name} />
    </>
    );
    }

    const Greeting = memo(function Greeting({ name }) {
    console.log('Greeting was rendered at', new Date().toLocaleTimeString());
    const [greeting, setGreeting] = useState('Hello');
    return (
    <>
    <h3>{greeting}{name && ', '}{name}!</h3>
    <GreetingSelector value={greeting} onChange={setGreeting} />
    </>
    );
    });

    function GreetingSelector({ value, onChange }) {
    return (
    <>
    <label>
    <input
    type="radio"
    checked={value === 'Hello'}
    onChange={e => onChange('Hello')}
    />
    Regular greeting
    </label>
    <label>
    <input
    type="radio"
    checked={value === 'Hello and welcome'}
    onChange={e => onChange('Hello and welcome')}
    />
    Enthusiastic greeting
    </label>
    </>
    );
    }
  3. 在有些情况下即使使用了 memo,哪怕参数没变,组件也有可能会被重新渲染

    • 当这个被记忆的组件的参数并不是来自于它的父组件时
    • 传入的参数是对象、数组、函数(所以对于参数是这些的组件 memo 失效),因此最直接的方法就是可以将该对象or数组里的元素解耦出来,作单个参数一个个传入。

useCallback

原型

1
const cachedFn = useCallback(fn, dependencies)
  1. 第一个参数类型为 function,一个回调函数;
  2. 第二个参数为一个列表,这个列表里的元素被称为依赖项,只有当这些依赖项更新时,这个回调函数才会执行并起作用,然后由 useCallback 返回。
  • 在每次重新渲染时,React会将第二个列表参数里的依赖项(元素)和上一次渲染的进行比较;如果都没有发生更改,useCallback 就不执行第一个回调函数;反之则执行。
  • 缓存的对象是父组件传给子组件的函数,相当于对函数做了缓存,当父组件重新渲染时,函数不会重新定义,不会返回一个新的函数(注意是返回这个函数,并没有调用它)从而不会重新渲染。
  • useCallback 要和 React.memo 配套使用,缺了一个都可能导致性能不升反而下降。

使用

  1. 当只是改变颜色主题时,跳过渲染。

    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
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    import { memo, useState, useCallback } from 'react';
    export default function App() {
    const [isDark, setIsDark] = useState(false);
    return (
    <>
    <label>
    <input
    type="checkbox"
    checked={isDark}
    onChange={e => setIsDark(e.target.checked)}
    />
    Dark mode
    </label>
    <hr />
    <ProductPage
    referrerId="wizard_of_oz"
    productId={123}
    theme={isDark ? 'dark' : 'light'}
    />
    </>
    );
    }

    function ProductPage({ productId, referrer, theme }) {
    const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
    referrer,
    orderDetails,
    });
    }, [productId, referrer]);

    return (
    <div className={theme}>
    <ShippingForm onSubmit={handleSubmit} />
    </div>
    );
    }

    function post(url, data) {
    // Imagine this sends a request...
    console.log('POST /' + url);
    console.log(data);
    }

    const ShippingForm = memo(function ShippingForm({ onSubmit }) {
    const [count, setCount] = useState(1);

    console.log('[ARTIFICIALLY SLOW] Rendering <ShippingForm />');
    let startTime = performance.now();
    while (performance.now() - startTime < 500) {
    // Do nothing for 500 ms to emulate extremely slow code
    }

    function handleSubmit(e) {
    e.preventDefault();
    const formData = new FormData(e.target);
    const orderDetails = {
    ...Object.fromEntries(formData),
    count
    };
    onSubmit(orderDetails);
    }

    return (
    <form onSubmit={handleSubmit}>
    <p><b>Note: <code>ShippingForm</code> is artificially slowed down!</b></p>
    <label>
    Number of items:
    <button type="button" onClick={() => setCount(count - 1)}>–</button>
    {count}
    <button type="button" onClick={() => setCount(count + 1)}>+</button>
    </label>
    <label>
    Street:
    <input name="street" />
    </label>
    <label>
    City:
    <input name="city" />
    </label>
    <label>
    Postal code:
    <input name="zipCode" />
    </label>
    <button type="submit">Submit</button>
    </form>
    );
    });
  2. 根据上一次回调中的状态更新下一次新的状态。

    1
    2
    3
    4
    5
    6
    7
    function TodoList() {
    const [todos, setTodos] = useState([]);
    const handleAddTodo = useCallback((text) => {
    const newTodo = { id: nextId++, text };
    setTodos(todos => [...todos, newTodo]);
    }, []);
    }

useMemo

原型

1
const cachedValue = useMemo(calculateValue, dependencies)

useCallback 很像,都是在依赖数据发生变化的时候,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用。

  • useCallback 缓存的结果是函数,主要用于缓存函数(没有调用这个函数)一般会是函数组件。
  • useMemo 缓存的结果是回调函数中return回来的值——计算结果的值;即函数被调用,得到这个函数调用后返回的值,然后再返回这个值。
  • 有两个参数:
    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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { useState, useMemo } from 'react';
const todos = createTodos();
export default function App() {
const [tab, setTab] = useState('all');
const [isDark, setIsDark] = useState(false);
return (
<>
<button onClick={() => setTab('all')}>
All
</button>
<button onClick={() => setTab('active')}>
Active
</button>
<button onClick={() => setTab('completed')}>
Completed
</button>
<br />
<label>
<input
type="checkbox"
checked={isDark}
onChange={e => setIsDark(e.target.checked)}
/>
Dark mode
</label>
<hr />
<TodoList
todos={todos}
tab={tab}
theme={isDark ? 'dark' : 'light'}
/>
</>
);
}

function TodoList({ todos, theme, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
return (
<div className={theme}>
<p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p>
<ul>
{visibleTodos.map(todo => (
<li key={todo.id}>
{todo.completed ?
<s>{todo.text}</s> :
todo.text
}
</li>
))}
</ul>
</div>
);
}

function createTodos() {
const todos = [];
for (let i = 0; i < 50; i++) {
todos.push({
id: i,
text: "Todo " + (i + 1),
completed: Math.random() > 0.5
});
}
return todos;
}

function filterTodos(todos, tab) {
console.log('[ARTIFICIALLY SLOW] Filtering ' + todos.length + ' todos for "' + tab + '" tab.');
let startTime = performance.now();
while (performance.now() - startTime < 500) {
// Do nothing for 500 ms to emulate extremely slow code
}

return todos.filter(todo => {
if (tab === 'all') {
return true;
} else if (tab === 'active') {
return !todo.completed;
} else if (tab === 'completed') {
return todo.completed;
}
});
}

useContext

原型

1
const value = useContext(SomeContext)

使用

  1. 各层组件之间上下搜索 ThemeContext 的值,找到最近的组件中的值并将其传给 theme,从而去改变CSS的样式。
    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
    import { createContext, useContext } from 'react';
    const ThemeContext = createContext(null);
    export default function MyApp() {
    return (
    <ThemeContext.Provider value="dark">
    <Form />
    </ThemeContext.Provider>
    )
    }
    function Form() {
    return (
    <Panel title="Welcome">
    <Button>Sign up</Button>
    <Button>Log in</Button>
    </Panel>
    );
    }
    function Panel({ title, children }) {
    const theme = useContext(ThemeContext);
    const className = 'panel-' + theme;
    return (
    <section className={className}>
    <h1>{title}</h1>
    {children}
    </section>
    )
    }
    function Button({ children }) {
    const theme = useContext(ThemeContext);
    const className = 'button-' + theme;
    return (
    <button className={className}>
    {children}
    </button>
    );
    }
  2. 在1的基础上,要做到更新主题,就可以将原来的 <ThemeContext.Provider value={theme}> 中的value的值改为 {theme},使用 useState 去更新它的值。即在父组件中声明一个状态变量,并将当前状态作为上下文值传递给 Provider
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function MyPage() {
    const [theme, setTheme] = useState('dark');
    return (
    <ThemeContext.Provider value={theme}>
    <Form />
    <Button onClick={() => {
    setTheme('light');
    }}>
    Switch to light theme
    </Button>
    </ThemeContext.Provider>
    );
    }
    如此就可以通过调用 setTheme 来更新传递给 Provider 的主题值,所有 Button 组件都将使用新的“light”值重新渲染。

创建React项目+Eslint+Prettier

  1. 通过以下命令创建和初始化一个webpack(还缺一个React)

    1
    npx webpack init

    init

  2. 安装react相关模块

    1
    2
    3
    4
    5
    npm i react react-dom --save
    npm i @types/react @types/react-dom --save-dev
    npm i eslint -D
    ./node_modules/.bin/eslint --init
    npm i eslint-config-prettier -D

    eslint

CATALOG
  1. 1. Hooks
  2. 2. 创建React项目+Eslint+Prettier