Skip to content

Redux

本小节,我们结合 React Native,一起来回顾一下 Redux 的使用。

打开 Redux 的官方,这里有一个快速入门的示例:https://redux.js.org/tutorials/quick-start

首先第一步,我们需要安装两个依赖,分别是 @reduxjs/toolkitreact-redux

npm install @reduxjs/toolkit react-redux

接下来,创建一个新的目录 redux,用于存放和 redux 操作相关的文件。在 reudx 目录下,我们创建一个 store 和 一个 reducers 文件,如下:

image-20220627111305026

store 文件中,我们从 @reduxjs/toolkit 中解构导出一个名为 configureStore 的函数,该函数可以生成一个 store 仓库,导出这个仓库。

在创建该仓库时,接收一个配置选项,其中有一项就是要配置的 reducer

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './reducers'
export default configureStore({
reducer: {
counter: counterReducer,
},
})

可以看到,reducer 是从当前目录下的另一个文件 reducers.js 中引入的,该文件的代码如下:

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
},
},
})
// Action creators are generated for each case reducer function
export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer

在上面的代码中,我们依然是从 @reduxjs/toolkit 中解构出了一个 createSlice 的方法,调用该方法时传入了一个配置项,在该配置项中,就有对应的 name、initialState、reducers,最后将该方法的返回值以及整个 reducer 对象还有 reducer 中各个方法导出。

接下来,我们单独书写一个组件,叫做 Counter,代码如下:

import { StyleSheet, Text, View, Button } from 'react-native'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from '../redux/reducers'
export default function Counter() {
const count = useSelector((state) => state.counter.value)
const dispatch = useDispatch()
return (
<View style={styles.container}>
<View style={styles.counterContainer}>
<Button title="-" onPress={() => dispatch(decrement())}></Button>
<Text style={styles.count}>{count}</Text>
<Button title="+" onPress={() => dispatch(increment())}></Button>
</View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
counterContainer: {
flexDirection: 'row',
alignItems: 'center',
},
count: {
marginLeft: 30,
fontSize: 30,
marginRight: 30,
},
})

在上面的代码中,我们从 react-redux 中解构出了 useSelector 以及 useDispatch 这两个方法,useSelector 类似于 React 中的 useState,而 useDispatch 很明显是用来触发 reducer 的。

最后,在 App.js 根组件中引入 Counter 组件,最外层包裹一个 ProviderProvider 中的 store 属性设置成导出的仓库对象即可。

import React from 'react'
import { Provider } from 'react-redux'
import store from './redux/store'
import Counter from './src/Counter'
// 导出 App 组件
export default function App() {
return (
<Provider store={store}>
<Counter></Counter>
</Provider>
)
}

至此,官方的一个基于 Redux 的计数器就完成了。

接下来我们来举一反三,来书写一个待办事项的示例。

在 src 目录中,新建三个子组件,分别是 Input、List、ToDoList,如下图:

image-20220627111331158

App.js 中,引入 ToDoList 组件:

import React from 'react'
import { Provider } from 'react-redux'
import store from './redux/store'
import ToDoList from './src/ToDoList'
// 导出 App 组件
export default function App() {
return (
<Provider store={store}>
<ToDoList></ToDoList>
</Provider>
)
}

ToDoList 组件本身由 InputList 这两个组件构成:

import { StyleSheet, View, Text } from 'react-native'
import Input from './Input'
import List from './List'
export default function ToDoList() {
return (
<View style={styles.container}>
<Text style={styles.title}>待办事项</Text>
<Input></Input>
<List></List>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
marginTop: 50,
},
title: {
fontSize: 20,
marginBottom: 10,
},
})

上面的 Input 组件主要负责内容输入,用户点击添加按钮后将输入框中的内容同步到仓库里面:

import { StyleSheet, TextInput, View, Button } from 'react-native'
import { useState } from 'react'
import { useDispatch } from 'react-redux'
import { increment } from '../redux/reducers'
export default function Input() {
const [inputValue, setInputValue] = useState('')
const dispatch = useDispatch()
function pressHandle() {
// 将输入的内容添加到状态数组中
dispatch(increment(inputValue))
setInputValue('')
}
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="What needs to be done?"
placeholderTextColor="#999"
autoCapitalize="none"
autoCorrect={false}
value={inputValue}
onChangeText={(text) => setInputValue(text)}
/>
<Button title="添加" onPress={pressHandle} />
</View>
)
}
const styles = StyleSheet.create({
container: {
marginBottom: 10,
flexDirection: 'row',
padding: 10,
justifyContent: 'flex-start',
alignItems: 'center',
},
input: {
width: 300,
backgroundColor: '#FFF',
height: 40,
padding: 10,
marginHorizontal: 10,
borderRadius: 3,
borderWidth: 1,
borderColor: '#DDD',
},
})

而下面的 List 组件,要做的事情稍微多一些,首先是要将仓库中获取到的待办事项项目全部渲染出来,然后本身还要对应点击和长按这两个事件,代码如下:

import {
StyleSheet,
Text,
View,
TouchableWithoutFeedback,
Alert,
} from 'react-native'
import { useSelector, useDispatch } from 'react-redux'
import { changeCompleteStatus, decrement } from '../redux/reducers'
export default function List() {
const todolist = useSelector((state) => state.counter.listItem)
const dispatch = useDispatch()
function pressHandle(index) {
dispatch(changeCompleteStatus(index))
}
function longPressHandle(index) {
Alert.alert('通知', '你是否要删除这一条待办事项', [
{
text: '取消',
onPress: () => console.log('取消删除'),
style: 'cancel',
},
{
text: '删除',
onPress: () => {
dispatch(decrement(index))
},
},
])
}
const items = todolist.map((item, index) => {
return (
<View key={index} style={styles.item}>
{item.isCompleted ? (
<TouchableWithoutFeedback
onPress={() => pressHandle(index)}
onLongPress={() => longPressHandle(index)}
>
<Text style={styles.complete}>{item.title}</Text>
</TouchableWithoutFeedback>
) : (
<TouchableWithoutFeedback
onPress={() => pressHandle(index)}
onLongPress={() => longPressHandle(index)}
>
<Text>{item.title}</Text>
</TouchableWithoutFeedback>
)}
</View>
)
})
return <View style={styles.container}>{items}</View>
}
const styles = StyleSheet.create({
container: {
marginBottom: 10,
},
item: {
padding: 10,
marginBottom: 10,
textAlign: 'center',
borderBottomWidth: 1,
borderColor: '#ccc',
width: 300,
},
complete: {
textDecorationLine: 'line-through',
},
})

最后是关于 reducer 这一块,修改和添加了对象的 reducer 方法,如下:

import { createSlice } from '@reduxjs/toolkit'
export const counterSlice = createSlice({
name: 'todolist',
initialState: {
listItem: [
{
title: '看书',
isCompleted: false,
},
{
title: '写字',
isCompleted: false,
},
{
title: '玩游戏',
isCompleted: true,
},
],
},
reducers: {
increment: (state, action) => {
let arr = [...state.listItem]
arr.push({
title: action.payload,
isCompleted: false,
})
state.listItem = arr
},
changeCompleteStatus: (state, action) => {
let arr = [...state.listItem]
arr[action.payload].isCompleted = !arr[action.payload].isCompleted
state.listItem = arr
},
decrement: (state, action) => {
let arr = [...state.listItem]
arr.splice(action.payload, 1)
state.listItem = arr
},
},
})
// Action creators are generated for each case reducer function
export const { increment, decrement, changeCompleteStatus } =
counterSlice.actions
export default counterSlice.reducer