本小节,我们结合 React Native,一起来回顾一下 Redux 的使用。
打开 Redux 的官方,这里有一个快速入门的示例:https://redux.js.org/tutorials/quick-start
首先第一步,我们需要安装两个依赖,分别是 @reduxjs/toolkit 和 react-redux
npm install @reduxjs/toolkit react-redux接下来,创建一个新的目录 redux,用于存放和 redux 操作相关的文件。在 reudx 目录下,我们创建一个 store 和 一个 reducers 文件,如下:
在 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 functionexport 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 组件,最外层包裹一个 Provider,Provider 中的 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,如下图:
在 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 组件本身由 Input 和 List 这两个组件构成:
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 functionexport const { increment, decrement, changeCompleteStatus } = counterSlice.actions
export default counterSlice.reducer