React和状态管理库
用一个词来形容状态管理库。
为了解决在React或Vue中传递state的游戏而诞生的东西。
总体来说,Redux的解决方案是让大家共同管理一个公共的留言板以管理状态。
为什么要使用状态管理库?
如何使用状态管理库可能需要查阅来了解,但要理解使用状态管理库的目的,则首先需要了解与React相关的知识,而不仅仅是了解状态管理库。
例如,可以将状况管理库的功能总结如下:
-
- グローバルステートの管理
-
- props drillingの緩和
- 不要なコンポーネントの更新をなくす
…等等
这些优点都是基于React解决某些挑战的,但是如果没有丰富的React开发经验,很难理解。
为什么要管理状态?
当谈到状态管理库时,显然它对于状态管理非常有用。
但首先我们要弄清楚,在什么情况下我们需要管理状态?
本文将对此进行调查研究。
请将状态管理库视为一个可以管理整个状态的库。
此外,我们希望您将React的状态基本上视为本地状态。
当我们尝试将其与其他组件共享时,
会涉及以下与React相关的主题。
-
- コンポーネントの親子関係とは
-
- 再レンダリングとは?
-
- useStateで何ができる?
-
- useContextで何ができる?
- props drillingとは?
在使用状态管理库之前,应该先理解这篇文章整理出来的这些知识。最后还会稍微介绍一下Redux。
组件的父子关系是什么意思
子元素被嵌套在父元素中。嵌套的是父元素。
就是这样。
props基本上只能从父元素传递给子元素。
渲染是指将图像或影像以视觉效果呈现出来的过程。
当state或props发生变化时,React会进行组件的更新过程。总结这个过程如下所示。
-
- まずReactが仮想DOMに新しい状態を反映する。
-
- その時古い方の仮想DOMと比較する。
- 変わったところだけ実際のDOMに反映する。
你可以把React的渲染看作是组件的更新。
备考
顺便提一下,加载DOM是由浏览器完成的。
以React所提供的组件更新方式来说,不是通过浏览器加载DOM。
只是在过去的(英文)文档中似乎有一段时间这两个术语被混合使用。
参考:React文档的翻译者经常在下面的文章评论区出现。
最近似乎决定将浏览器的DOM加载称为“绘制”。
useState是什么意思
需要使用 state 的原因有两个。
-
- 本地变量在渲染之间不会持续存在
- 本地变量不会触发渲染
如果有这样的东西,就可以更新UI了呢。
-
- 在渲染过程中持有数据的状态变量
- 使React能够重新加载组件的函数
而且,这正是useState所提供的东西。
组件的更新
当组件的状态发生变化时,该组件的外观会被更新。
另外,当父组件被更新时,嵌套在其中的子组件也会被更新。
如同参考文章所述,若要避免组件更新,则存在一种模式。
分析 props drilling 是一种使用物件辅助学习或练习的方式。
你还记得props只能从父级组件传递给子级组件吗?
子组件需要父组件进行嵌套。
换句话说,当出现在嵌套组件中传递props给“嵌套的组件中的嵌套的组件”这样的情况。
这样深层传递props的现象被称为props drilling。
useContext 是什么?
在解决props drilling问题时,一种方法是从远离的父组件中获取数据的深度嵌套组件。在上下文中,获取数据可以类比为“跃迁”。与逐级传递props不同,这种方法更加便捷。
请按以下步骤操作(详细信息请参考文档)。
-
- 使用createContext
-
- 父组件提供了该Provider
- 深层嵌套的组件使用了useContext
举个例子,有这样一段代码片段。
将https://playcode.io/react转为可复制粘贴并进行试验的代码。
import React, {useState} from 'react';
export function App() {
const [count, setCount] = useState(0)
return (
<div className='App'>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
这段代码非常简洁。
但为什么它简洁呢?因为这里只存在局部状态。
当局部状态增加时,计数也会增加。
计数这个状态不会离开这个组件。
要在其他组件中使用count的状态,应该怎么做?
如果有很多想要共享的组件,一种方法是先创建子组件并将其作为props传递。
还有两个选择。
-
- Contextを使う
- Reduxなどの状態管理ライブラリを使う
终于能够充分利用状态管理库的优势了。
简单来说,状态管理库与React提供的useContext有相似的目的
做的事情变多 + 还会带来相应的好处
使用Redux,可以消除组件中操作state的代码(将其分离到另一个文件中),这样就能达到这种感觉。
用Redux在之前的代码中尝试一下
如果为了一个只是通过按下按钮来增加或减少计数的应用而特意使用Redux,会有什么结果呢?会将其拆分成App.js、action.js和reducer.js三个部分。
首先从导入部分开始
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from './action';
请注意,我们从”./action”文件中导入了”increment”和”decrement”两个函数。
此外,我们还从react-redux中导入了名为”useSelector”和”useDispatch”的使用相关内容。
关于useSelector和useDispatch
我将几乎直接引用这篇文章的说明。
-
- useSelector…stateに変更があったら自動的に再実行され、コンポーネントを再描画する
-
- useDispatchはdispatch関数を返す
- dispatch関数は、”action”を引数にとる。実行すると、stateが更新される
function App() {
const count = useSelector(state => state.count);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
<button onClick={() => dispatch(decrement())}>Decrement</button>
// onClickのところでactionを使う
</div>
);
}
export default App;
可以知道dispatch函数被用来发出action。
查看action的定义
export const increment = () => {
return {
type: 'INCREMENT'
};
};
export const decrement = () => {
return {
type: 'DECREMENT'
};
};
在这里指定了一个不知所云的类型:“INCREMENT”。
增量(increment)和减量(decrement)实际上是普通的 JavaScript 函数。
使用增量和减量函数的文件。
const initialState = {
count: 0
};
export const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
先前的指定是在這種邏輯分支中使用的。
這就是 Redux 的優點,可以將狀態操作完全分離到不同的文件中。
如果state未被定义,那么将使用initialState作为第一个参数state的赋值。
顺便说一下,关于这个 counterReducer,我认为在之前的代码中没有使用过。
本文省略了解释。
请阅读有关 Redux 的文章。
对于希望先复制粘贴然后试试的您
将以下代码放入同一目录中,将index.html拖放到浏览器中即可运行。
从ChatGPT获得的文章据说如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simple React-Redux Example</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.4/react-redux.min.js"></script>
</head>
<body>
<div id="root"></div>
<script src="./app.js"></script>
</body>
</html>
// action.js
const increment = () => ({
type: 'INCREMENT',
});
const decrement = () => ({
type: 'DECREMENT',
});
// reducer.js
const initialState = {
count: 0,
};
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
// Store
const { createStore } = Redux;
const store = createStore(counterReducer);
// App.js
const App = () => {
const dispatch = ReactRedux.useDispatch();
const count = ReactRedux.useSelector((state) => state.count);
return React.createElement(
'div',
null,
React.createElement('h1', null, `Count: ${count}`),
React.createElement('button', { onClick: () => dispatch(increment()) }, 'Increment'),
React.createElement('button', { onClick: () => dispatch(decrement()) }, 'Decrement')
);
};
// index.js
const { Provider } = ReactRedux;
ReactDOM.render(
React.createElement(Provider, { store }, React.createElement(App)),
document.getElementById('root')
);