docs: React note update

This commit is contained in:
ZiuChen 2023-04-25 18:54:40 +08:00
parent 3dd5d155d7
commit d751d61235

View File

@ -3594,11 +3594,11 @@ export const AppWrapper = styled.div`
由于本质上是方法调用,返回的内容本质上是一个组件,我们可以通过给组件传值,很方便的实现动态样式绑定:
点击按钮动态修改样式的值
点击按钮动态修改样式的值
```tsx
// App.jsx
import React, { PureComponent } from 'react'
import React, { PureComponent } from 'react'··
import { AppWrapper } from './styles/style'
export default class App extends PureComponent {
@ -3657,5 +3657,457 @@ export const AppWrapper = styled.div`
`
```
我们之前演示过,标签模板字符串中通过`${ ... }`动态插入的变量会作为参数,当函数调用时传递给函数
创建组件时动态传入的这些函数当props发生变化时就会在`div`函数中被再次调用,生成新的样式并应用到页面中
### styled高级特性
- 支持样式的继承
- 支持设置主题
```tsx
// 样式继承
const BasicButton = styled.button`
padding: 8px 30px;
border-radius: 5px
`
const WarnButton = styled(BasicButton)`
background-color: orange;
color: white;
`
```
```tsx
import { ThemeProvider } from 'styled-components'
...
<ThemeProvider
theme={{
color,
hoverColor,
size
}}
>
...
</ThemeProvider>
...
```
### classnames
动态绑定className如果没有`classnames`我们常常写这样的代码来动态绑定className
```tsx
const classList = ['item', this.state.isActive ? 'item-active' : '']
const className = classList.join(' ')
// or
const className = `item ${this.state.isActive ? 'item-active' : ''}`
// or
const classNameMap = {
'item': true,
'item-active': this.state.isActive
}
const className = Object.entries(classNameMap).map(item => !!item[1] ? item[0] : '').join(' ')
```
手动编写动态绑定的className较为繁琐可以依靠第三方库`classnames`帮我们完成Vue已将类似的功能内置
```bash
npm i classnames
```
Usage Sample:
```tsx
classNames('foo', 'bar')
classNames('foo', { bar: true })
classNames(...['foo', 'bar'])
```
## Redux
- Redux的核心思想
- Redux的基本使用
- React结合Redux
- Redux的异步操作
- redux-devtool
- reducer的模块拆分
### 理解JavaScript的纯函数
- 函数式编程中有一个非常重要的概念 **纯函数**JavaScript符合函数式编程的范式所以也有纯函数的概念
- 在React开发中纯函数被多次提及
- React组件被要求像一个纯函数为什么是像因为还有类组件
- Redux中有一个reducer的概念同样是要求必须是一个纯函数
- 掌握纯函数对于理解很多框架的设计都是有帮助的
一个纯函数必然具备以下特征:
- 确定的输入一定产生确定的输出
- 函数的执行过程中,不能产生副作用
### 为什么需要Redux
- JS需要管理的状态越来越多越来越复杂
- 状态不断发生变化之间又相互依赖,这要求视图层也能同步更新
- React提供了自动更新视图的方法但状态仍需要手动管理
- Redux可以帮我们管理状态提供了**可预测的状态管理**
- 框架无关体积只有2KB大小
### Redux的核心理念
Redux的核心理念 Store
- 定义一个统一的规范来操作数据,这样就可以做到对数据的跟踪
- `list.push()` `list[0].age = 18`
Redux的核心理念 Action
- Redux要求要修改数据必须通过Action来修改
- 所有数据的变化必须通过派发PatchAction来更新
- Action是一个普通的JS对象用来描述此次更新的type与content
- `const action = { type: 'ADD_ITEM', item: { name: 'Ziu', age: 18 } }`
Redux的核心理念 Reducer
- 如何将Store和Action联系在一起
- reducer是一个纯函数
- 完成的工作就是将传入的state和action结合起来生成一个新的state
- `patch` => `reducer` => `newState` => `Store`
### Redux Demo
下例中,通过`createStore`创建了一个Store已经不推荐了
- initialState用于在调用`createStore`时作为默认值传入`reducer`
- 后续每次`store.dispatch`都会调用`reducer`
- 通过`reducer`更新state中的数据
在React中可以通过`store.subscribe`注册State变化的监听回调
- 当state发生变化时通过调用`this.forceUpdate`触发组件的更新
- 一般情况下,我们在`componentDidMount`注册监听回调,在`componentWillUnmount`解除监听
::: code-group
```tsx [App.jsx]
// App.jsx
import React, { PureComponent } from 'react'
import store from './store'
export default class App extends PureComponent {
componentDidMount() {
// Subscribe to the store
store.subscribe(() => {
console.log('subscribe', store.getState())
this.forceUpdate()
})
}
componentWillUnmount() {
store.unsubscribe()
}
render() {
return (
<div>
<h1>App</h1>
<p>Count: {store.getState().count}</p>
<p>Name: {store.getState().name}</p>
<button onClick={() => store.dispatch({ type: 'INCREMENT' })}> +1 </button>
<button onClick={() => store.dispatch({ type: 'DECREMENT' })}> -1 </button>
<button onClick={() => store.dispatch({ type: 'CHANGE_NAME', name: 'ZIU' })}>
{' '}
CHANGE_NAME{' '}
</button>
</div>
)
}
}
```
```tsx [index.js]
// store/index.js
import { createStore } from 'redux'
// The initial application state
// This is the same as the state argument we passed to the createStore function
const initialState = {
count: 0,
name: 'Ziu'
}
// Reducer: a pure function that takes the previous state and an action, and returns the next state.
// (previousState, action) => newState
function reducer(state = initialState, action) {
console.log('reducer', state, action)
switch (action.type) {
case 'INCREMENT':
// NOTE: Keep functions pure - do not mutate the original state.
// Desctructure the state object and return a **new object** with the updated count
// Instead of `return state.count++`
return {
...state,
count: state.count + 1
}
case 'DECREMENT':
return {
...state,
count: state.count - 1
}
case 'CHANGE_NAME':
return {
...state,
name: action.name
}
default:
return state
}
}
const store = createStore(reducer)
export default store
```
:::
### 进一步封装
可以将耦合在一起的代码拆分到不同文件中
- 将`reducer`抽取出来`reducer.js`,简化`store/index.js`内容
- 将`action.type`抽取为常量`constants.js`,使用时做导入,以保证一致性
- 将`action`抽取出来`actionFactory.js`用于外部dispatch时规范类型
::: code-group
```tsx [index.js]
// store/index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
```
```tsx [constants.js]
// constants.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'
export const CHANGE_NAME = 'CHANGE_NAME'
```
```tsx [reducer.js]
// reducer.js
import * as actionType from './constants'
const initialState = {
count: 0,
name: 'Ziu'
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case actionType.INCREMENT:
return {
...state,
count: state.count + 1
}
case actionType.DECREMENT:
return {
...state,
count: state.count - 1
}
case actionType.CHANGE_NAME:
return {
...state,
name: action.name
}
default:
return state
}
}
```
```tsx [actionFactory.js]
// actionFactory.js
import * as actionType from './constants'
export const increment = () => ({
type: actionType.INCREMENT
})
export const decrement = () => ({
type: actionType.DECREMENT
})
export const changeName = (name) => ({
type: actionType.CHANGE_NAME,
name
})
```
:::
```tsx
// App.jsx
import React, { PureComponent } from 'react'
import store from './store'
import { increment, decrement, changeName } from './store/actionFactory'
export default class App extends PureComponent {
componentDidMount() {
store.subscribe(() => this.forceUpdate())
}
componentWillUnmount() {
store.unsubscribe()
}
render() {
return (
<div>
<h1>App</h1>
<p>Count: {store.getState().count}</p>
<p>Name: {store.getState().name}</p>
<button onClick={() => store.dispatch(increment())}> +1 </button>
<button onClick={() => store.dispatch(decrement())}> -1 </button>
<button onClick={() => store.dispatch(changeName('ZIU'))}>CHANGE_NAME</button>
</div>
)
}
}
```
### Redux的三大原则
单一数据源
- 整个应用程序的状态都被存储在一棵Object Tree上
- 且这个Object Tree只存储在一个Store中
- 但Redux并不强制限制创建多Store不利于数据维护
- 单一数据源有利于整个应用程序的维护、追踪、修改
State属性是只读的
- 允许修改State的方法只有patch action不要直接修改State
- 确保了View或网络请求都不能修改State
- 保证所有的修改都能被追踪、按照严格的顺序执行不用担心竞态race condition的问题
使用纯函数来执行修改
- 通过reducer将旧State与新State联系在一起并且返回一个**新的State**
- 随着应用程序复杂程度增加可以将reducer拆分为多个小的reducer分别用于操作不同State Tree的某一部分
- 所有的reducer都应该是纯函数不能产生任何的副作用
### 优化重复代码
当编写了一些案例的时候会发现React结合Redux时会编写很多重复的代码
在每个需要用到Redux中状态的组件中都需要在不同生命周期做添加订阅/解除订阅的处理组件初始化时还要从store中取最新的状态
针对重复代码的问题,可以使用之前学到的高阶组件来做优化
Redux官方提供的库`react-redux`可以让我们更方便的在React中使用Redux
```bash
npm i react-redux
```
在Profile组件中通过高阶函数`connect`实现的
将store中需要的状态通过`mapStoreToProps`转为props并将需要使用store中状态的组件传入调用connect返回的函数中
`Profile`组件中就可以从props中获取到store中的状态
::: code-group
```tsx [App.jsx]
// App.jsx
import React, { PureComponent } from 'react'
import { Provider } from 'react-redux'
import store from './store'
import Profile from './Profile'
export default class App extends PureComponent {
render() {
return (
<Provider store={store}>
<div>
<h2>App</h2>
<Profile></Profile>
</div>
</Provider>
)
}
}
```
```tsx [Profile.jsx]
// Profile.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
// mapStateToProps is a function that
// takes the state of the store as an argument
// and returns an object with the data that the component needs from the store.
// component will receive the data as props.
const mapStateToProps = (state) => ({
count: state.count
})
export default connect(mapStateToProps)(
class Profile extends Component {
render() {
return (
<div>
<h2>Profile</h2>
<p>Count: {this.props.count}</p>
</div>
)
}
}
)
```
:::
我们刚刚只是完成了对State的映射将Store中保存的全局状态state映射到了Profile组件的props中
connect还可以传入第二个参数用于将action也映射到props中
```tsx {4,10-13,17,25,26}
// Profile.jsx
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { INCREMENT, DECREMENT } from './store/constants'
const mapStateToProps = (state) => ({
count: state.count
})
const mapDispatchToProps = (dispatch) => ({
increment: () => dispatch({ type: INCREMENT }),
decrement: () => dispatch({ type: DECREMENT })
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(
class Profile extends Component {
render() {
return (
<div>
<h2>Profile</h2>
<p>Count: {this.props.count}</p>
<button onClick={this.props.increment}> +1 </button>
<button onClick={this.props.decrement}> -1 </button>
</div>
)
}
}
)
```
本质上是`connect`内部对操作进行了封装,把逻辑隐藏起来了:
- 调用`connect`这个**高阶函数**,返回一个**高阶组件**
- 为高阶组件传入映射目标组件,最后高阶组件返回一个新组件
- 新组件的props包含了来自Store中状态/dispatch的映射