前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >『Dva』管理数据

『Dva』管理数据

原创
作者头像
BNTang
发布2024-01-23 00:15:42
1812
发布2024-01-23 00:15:42
举报
文章被收录于专栏:『Ts + React』项目实战

一、前言

🐤本篇文章是『从零玩转 TypeScript + React 项目实战』系列文章的第 4 篇,主要介绍『Dva』管理数据

通过上一篇文章的学习,我们已经知道了『Dva』是什么,以及『Dva』的使用方式,如何使用『Dva』来渲染我们的组件,其实 dva 的主用作用并不是用来渲染组件的,它的主要作用是对 redux、redux-saga 进行封装,它的作用就是用来管理数据的,那么我们就来看一下『Dva』是如何管理数据的。

二、管理数据

那么如何使用『Dva』来管理数据呢?要想使用『Dva』来管理数据,我们需要先了解一下『Dva』的核心,『Dva』的核心有三个,分别是:

    1. model,也就是说我们可以给每一个组件定义一个 model,然后在这个 model 中就可以保存对应组件的数据,就可以保存对应组件的 reducer、就可以保存对应组件的负作用,就可以保存对应组件订阅的内容:

所以说知道了这个 model 之后要想使用 dva 来管理数据,那么我们就需要先定义一个 model。

1.定义 Model

那么如何定义一个 model 呢?我先不管三七二十一,我先定义一个 Home 组件:

代码语言:diff
复制
/* index.js */
+ function Home() {
+     return (
+         <div>
+             Home
+         </div>
+     )
+ }

接下来我的要求是定义一个 model,用这个 model 来保存 Home 组件的数据,来保存 Home 组件的 reducer、来保存 Home 组件的负作用、来保存 Home 组件订阅的内容,那么如何定义呢?

指定命名空间

model 其实就是一个特殊的对象,那特殊在哪里呢?特殊在他有一些特殊的 key,比如说它里面,有一个叫做 namespace 的这么一个 key,通过这个 key,我们可以指定对应 model 的命名空间,这里我定义一个叫 homeModel 的 model,然后我在这个 model 中通过 namespace 来指定这个 model 的命名空间是 home

代码语言:diff
复制
/* index.js */
+ let homeModel = {
+     namespace: 'home'
+ }

为什么要指定命名空间呢?因为将来我们是可以定义多个 model 的,例如这里我在新增一个 model 叫做 aboutModel,然后我在这个 model 中通过 namespace 来指定这个 model 的命名空间是 about

代码语言:diff
复制
/* index.js */
+ let aboutModel = {
+     namespace: 'about'
+ }

将来呢,我们就可以通过命名空间来区分当前使用的到底是哪一个 model,反正你只需要知道 model 就是一个特殊的对象,这个对象里面有一个特殊的 key,叫做 namespace,通过这个 key,我们可以指定对应 model 的命名空间,命名空间的作用就是用来区分不同的 model。

保存数据

那么接下来呢,我们就可以在这个 model 中保存数据了,那么如何保存数据呢?在这个特殊的对象中,还有一个特殊的 key,叫做 state,通过这个 key,我们可以保存对应 model 的数据,例如我在 homeModel 中定义一个 state,然后我在这个 state 中定义一个 count,这个 count 的值是 0:

代码语言:diff
复制
/* index.js */
 let homeModel = {
     namespace: 'home',
+    state: {
+        count: 0
+    },
 }

如果说这里要写上一段注释信息的话,那么就是:指定当前命名空间保存的数据。

处理数据

保存完毕数据之后我们还要干嘛?是不是要处理数据,这里可以通过 reducer 来处理,所以说这里我就要给当前的 model 定义一个 reducer,那么如何定义呢?在这个特殊的对象中,还有一个特殊的 key,叫做 reducers,通过这个 key,我们可以定义对应 model 的 reducer,例如我在 homeModel 中定义一个 reducer:

代码语言:diff
复制
/* index.js */
 let homeModel = {
     namespace: 'home',
     state: {
         count: 0
     },
+    reducers: {
+    }
 }

reducers 定义好了,那么 reducers 中是不是要定义一些处理方法,比如说我在这里定义一个叫做 add 的处理方法,这个方法对应一个函数,这个函数接收两个参数,第一个参数是 state,第二个参数是 action,state 是过去的状态,也就是过去的值,action 是当前的动作,也就是当前派发的动作,和过去中的 reducer 是一样的。

代码语言:diff
复制
/* index.js */
 let homeModel = {
     namespace: 'home',
     state: {
         count: 0
     },
     reducers: {
+        add(state, action) {
+        }
     }
 }

那么 reducer 中的 key 有什么作用呢?reducer 中 key 的作用,其实是用来指定当前 reducer 的处理方法的,也就是类型,例如我上面定义的一个 key 叫做 add,代表着将来别人派发 type 是 add playload 是什么 xx 的时候 {type: add, playload: xxx},这个时候就会去 reducers 中找到 key 是 add 的这个哥们来去执行。

再比如说,别人派发一个 sub,这个时候就会去 reducers 中找到 key 是 sub 的这个哥们来去执行,发现没有,那么就不会执行任何操作,如果说有,它就会执行对应的操作,所以说 reducers 中的 key 的作用,其实是用来指定当前 reducer 的处理方法的,也就是类型。

好介绍清楚了,那么接下来我们就可以在这个 add 方法中处理数据了,在 add 中需要干什么呢?我这里就简单的将 count 加 1,然后将这个 count 返回出去,代码的写法就是:

代码语言:diff
复制
/* index.js */
let homeModel = {
    namespace: 'home',
    state: {
        count: 0
    },
    reducers: {
        add: (state, action) => {
+           return {
+               count: state.count + action.count
+           }
        }
    }
}

我就添加了三行代码,返回了一个对象,这个对象中有一个 count,这个 count 的值是上一次的 count 加上这一次 action 当中你带过来你让他递增的数,这样就可以了。

紧接着再添加一个 sub 方法,这个方法的作用就是将 count 减 1,然后将这个 count 返回出去,代码的写法就是:

代码语言:diff
复制
/* index.js */
let homeModel = {
    namespace: 'home',
    state: {
        count: 0
    },
    reducers: {
        add: (state, action) => {
            return {
                count: state.count + action.count
            }
        },
+       sub: (state, action) => {
+           return {
+               count: state.count - action.count
+           }
+       }
    }
}

只需要将加法改成减法就可以了,到此为止关于 model 的定义就结束了,那么接下来我们就可以使用这个 model 了。

2.注册 Model

定义好了之后我们要在哪里使用这个 model 呢?是不是要在 dva 中进行使用,那怎么告诉 dva 我们要使用这个 model 呢?

很简单,通过 dva 创建出来的实例当中有一个 model 方法,这一步我称之为告诉 dva 需要使用哪个 model,那么如何告诉呢?就是通过 dva 创建出来的实例当中的 model 方法,这个方法接收一个对象,这个对象就是我们定义的 model,例如我在这里告诉 dva 我要使用 homeModel:

代码语言:diff
复制
/* index.js */
+ app.model(homeModel)

app.model 是可以多次调用的,例如我在这里再调用一次,告诉 dva 我既要使用 homeModel,也要使用 aboutModel:

代码语言:diff
复制
/* index.js */
+ app.model(aboutModel)

3.连接 Model

本文简单点只来一个,通过如上的介绍我是不是已经告诉 dva 我要在 dva 中使用 homeModel 了,那么接下来我们就可以在 dva 中使用 homeModel 了。

接下来我又要说到 dva 的本质了,dva 的本质是对 redux、redux-saga 进行封装,那既然是对 redux 进行封装,这个时候在 saga 中想要使用保存在 homeModel 中的数据,想要使用 homeModel 中的 reducer,是不是要通过 connect 连接起来,在回到我前面介绍的那张图:

再次观察上图,我们的 model 已经定义好了,在从图中可以看到是不是还要通过 saga 的 connect 把 model 和组件连接起来,这个时候才能在组件里面使用 Model 当中保存的数据,这个时候才能在组件当中派发任务,派发 action,去修改 Model 当中保存的数据。

所以接下来就是通过 connect 把 homeModel 和 Home 组件关联起来,过去讲解 saga 的时候已经介绍过了,是不是需要分别定义 mapStateToProps 和 mapDispatchToProps:

代码语言:diff
复制
  /* index.js */
+ // 在mapStateToProps方法中告诉React-Redux, 需要将store中保存的哪些数据映射到当前组件的props上
+ const mapStateToProps = (state) => {
+     return {
+     }
+ };
+ // 在mapDispatchToProps方法中告诉React-Redux, 需要将哪些派发的任务映射到当前组件的props上
+ const mapDispatchToProps = (dispatch) => {
+     return {
+     }
+ };

然后通过 connect 把 mapStateToProps 和 mapDispatchToProps 和 Home 组件关联起来,要使用 connect 首先需要导入 connect,前面说了 dva 是对 redux、redux-saga 进行封装,所以说 connect 是从 redux 中导入的,这里可以直接从 dva 中导入 connect:

代码语言:diff
复制
/* index.js */
+ import {connect} from 'dva';

然后通过 connect 把 mapStateToProps 和 mapDispatchToProps 和 Home 组件关联起来:

代码语言:diff
复制
/* index.js */
+ st AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);

connect 需要调用两次,后面一次是要连接的那个组件,前面一次要把 mapStateToProps 和 mapDispatchToProps 传给他,传给他之后,他就会把 mapStateToProps 和 mapDispatchToProps 传给你。

然后你就可以从 state 里面获取 count,在这里有一个注意点,在前面我说过将来我们是有可能定义多个 model,多个 model 中,是不是都有可能保存数据,例如,我在项目中在加一个 aboutModel:

代码语言:javascript
复制
let aboutModel = {
    namespace: 'about',
    state: {
        num: 0
    },
    reducers: {
        add: (state, action) => {
            return {
                num: state.num + action.num
            }
        },
        sub: (state, action) => {
            return {
                num: state.num - action.num
            }
        }
    }
}

我在 model 中定义了一个 num,并且定义了对应的 reducers,然后在 dva 中注册一下 model,告诉 dva 我要使用 aboutModel:

代码语言:diff
复制
/* index.js */
+ app.model(aboutModel)

现在我们是不是在 dva 中弄了多个 model,那么在 mapStateToProps 中映射数据,我怎么知道当前映射的这个数据是 homeModel 的还是 aboutModel 当中的呢?很简单,在 homeModel 与 aboutModel 中是不是都有一个 namespace,所以我们在获取的时候该如何获取呢?

4.使用 Model

在 mapStateToProps 中会给我一个 state,要从 state 中的 namespace 中拿到我们的数据所以 mapStateToProps 的写法就是:

代码语言:diff
复制
/* index.js */
  const mapStateToProps = (state) => {
+     return {
+         count: state.home.count
+     }
  };

改造为如上的写法,这个步骤总结下来就是需要从传入的 state 命名空间中拿到对应 Model 保存的数据。state.home.count 就代表着我现在要拿到的是命名空间是 home 的这个 model 当中保存的 count 这个数据。

经过了这一步我们的数据就已经有了,接下来就是完善一下派发的方法了,更改 mapDispatchToProps,在当中定义一个 increment 方法,mapDispatchToProps 中默认会传递一个 dispatch,这个 dispatch 就是用来派发任务的,然后在 increment 方法中利用 dispatch 派发一个任务,这个任务的 type 是 add:

代码语言:diff
复制
/* index.js */
const mapDispatchToProps = (dispatch) => {
    return {
+        increment() {
+            dispatch({type: 'add'});
+        }
    }
};

将来在组件中调用 increment 方法的时候,就会派发一个 type 是 add 的任务,然后找到 homeModel 当中的 reducers,在 reducers 中找到 key 是 add 的这个哥们来去执行,然后在这个哥们中就可以修改 count 了,当然还要给这个派发的任务传递一些数据还要改造一下:

代码语言:diff
复制
/* index.js */
const mapDispatchToProps = (dispatch) => {
    return {
        increment() {
+            dispatch({type: 'add', count: 1});
        }
    }
};

好了除了有 increment 方法之外,还有一个 decrement 方法,这个方法的作用就是派发一个 type 是 sub 的任务,然后在 reducers 中找到 key 是 sub 的这个哥们来去执行,然后在这个哥们中就可以修改 count 了:

代码语言:diff
复制
/* index.js */
const mapDispatchToProps = (dispatch) => {
    return {
        increment() {
            dispatch({type: 'add', count: 1});
        },
+        decrement() {
+            dispatch({type: 'sub', count: 1});
+        }
    }
};

到此为止,我们是不是就已经将 model 映射到 Home 组件了,映射到了 Home 组件,他是不是就可以传递给 Home 的 props 了,然后在 Home 组件中就可以使用了,例如在 Home 组件中我想要使用 count,那么我就可以通过 this.props.count 来获取:

代码语言:diff
复制
/* index.js */
+ function Home(props) {
    return (
        <div>
+            <p>{props.count}</p>
        </div>
    )
}

我是不是就可以编写两个按钮,一个按钮是加,一个按钮是减,然后在点击的时候调用 increment 和 decrement 方法,这个时候就可以修改 count 了,例如我在这里编写一个按钮,按钮的内容是 +,然后在点击的时候调用 increment 方法:

代码语言:diff
复制
/* index.js */
function Home(props) {
    return (
        <div>
            <p>{props.count}</p>
+            <button onClick={props.increment()}>+</button>
        </div>
    )
}

然后再编写一个按钮,按钮的内容是 -,然后在点击的时候调用 decrement 方法:

代码语言:diff
复制
/* index.js */
function Home(props) {
    return (
        <div>
            <p>{props.count}</p>
            <button onClick={props.increment()}>+</button>
+            <button onClick={props.decrement()}>-</button>
        </div>
    )
}

现在的这个代码是不是就会过去的代码一样,过去使用 reduxSage 的时候是不是也是这样的,那么现在我们使用 dva 的时候是不是就可以这样使用了,那么接下来我们来回顾一下,在回顾之前还需要完善一下代码,通过 connect 连接好了对应的组件之后返回了一个新的组件,这个组件就是 AdvHome,需要使用 AdvHome 组件,所以说在 App 组件中使用 AdvHome 组件:

代码语言:diff
复制
function App() {
    return (
        <div>
+            <AdvHome/>
        </div>
    );
}

最终的代码如下:

代码语言:javascript
复制
import dva, {connect} from 'dva';

const app = dva();

let homeModel = {
    namespace: 'home',
    state: {
        count: 666
    },
    reducers: {
        add: (state, action) => {
            return {
                count: state.count + action.count
            }
        },
        sub: (state, action) => {
            return {
                count: state.count - action.count
            }
        }
    }
}
let aboutModel = {
    namespace: 'about',
    state: {
        num: 0
    },
    reducers: {
        add: (state, action) => {
            return {
                num: state.num + action.num
            }
        },
        sub: (state, action) => {
            return {
                num: state.num - action.num
            }
        }
    }
}

app.model(homeModel);
app.model(aboutModel);

const mapStateToProps = (state) => {
    return {
        count: state.home.count
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
        increment() {
            dispatch({type: 'add', count: 1});
        },
        decrement() {
            dispatch({type: 'sub', count: 1});
        }
    }
};

function Home(props) {
    return (
        <div>
            <p>{props.count}</p>
            <button onClick={() => {
                props.increment()
            }}>+
            </button>
            <button onClick={() => {
                props.decrement()
            }}>-
            </button>
        </div>
    )
}
const AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);

function App() {
    return (
        <div>
            <AdvHome/>
        </div>
    );
}

app.router(() => <App/>);
app.start('#root');

运行项目,打开浏览器,可以看到如下的效果:

5.回顾

将上面的内容简单的回顾一下:

  1. 首先定义 Model,指定了 Model 对应的命名空间,指定了 Model 保存的数据,指定了 Model 的 reducer
  2. 在 Model 的 reducer 中定义了 add 与 sub 方法将来只要派发了 add 与 sub 这两个任务,就会去执行对应的方法
  3. 在 dva 中注册 Model,告诉 dva 我要使用哪个 Model,通过 dva 实例 .model 方法来注册 Model
  4. 在 dva 中连接 Model,通过 connect 方法将 Model 与组件连接起来,然后在组件中就可以使用 Model 中保存的数据了,在 connect 中将保存的数据与保存的方法映射到了 Home 中就是 const AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home); 这行代码
  5. 定义了一个 mapStateToProps 方法,这个方法的作用就是将 Model 中保存的数据映射到当前组件的 props 上,然后在组件中就可以通过 this.props.count 来获取到 Model 中保存的数据了
  6. 定义了一个 mapDispatchToProps 方法,这个方法的作用就是将 Model 中保存的方法映射到当前组件的 props 上,然后在组件中就可以通过 this.props.increment() 来调用 Model 中保存的方法了

好了回顾完毕之后再继续往下看,我们运行项目之后页面显示的是 666,原因很简单就是我 homeModel 中的 count 我修改为了 666,为什么会显示 666 呢,就是因为我在 mapStateToProps 中明确的指定了我要拿到的是 homeModel 中的 count,所以说这里是不是就是 homeModel 中的 count,那么如果说我想要拿到 aboutModel 中的 num,那么我是不是就可以在 mapStateToProps 中修改为:

代码语言:diff
复制
/* index.js */
const mapStateToProps = (state) => {
    return {
+        count: state.about.num
    }
};

回到浏览器刷新页面,可以看到页面显示的是 0:

显示没问题了,再分别点击 + 和 - 按钮,发现不行,那么为什么不行呢?原因很简单,获取数据的时候, 我们需要指定从哪一个命名空间的 Model 中获取, 但是在派发任务的时候, 我们没有指定派发到哪一个命名空间的 Model 中, 所以说问题就出现在这里,同理在派发任务的时候, 我们也需要指定要派发给哪一个命名空间的 Model。

就以我们现在的代码来看,他怎么知道 add 与 sub 这两个任务是派发到 Model 中的呢?所以说在派发任务的时候,我们也需要指定要派发给哪一个命名空间的 Model,那么如何指定呢?很简单,在 type 取值前面加上命名空间就可以了,例如我在这里指定 type 为 home/add

代码语言:diff
复制
/* index.js */
const mapDispatchToProps = (dispatch) => {
    return {
        increment() {
-            dispatch({type: 'add', count: 1});
+            dispatch({type: 'home/add', count: 1});
        },
        decrement() {
-            dispatch({type: 'sub', count: 1});
+            dispatch({type: 'home/sub', count: 1});
        }
    }
};

记得将 mapStateToProps 中代码改为 homeModel,本文基于 homeModel 进行效果测试演示,运行项目,打开浏览器,可以看到如下的效果:

最后附上完整代码:

代码语言:javascript
复制
import dva, {connect} from 'dva';

const app = dva();

let homeModel = {
    namespace: 'home',
    state: {
        count: 666
    },
    reducers: {
        add: (state, action) => {
            return {
                count: state.count + action.count
            }
        },
        sub: (state, action) => {
            return {
                count: state.count - action.count
            }
        }
    }
}
let aboutModel = {
    namespace: 'about',
    state: {
        num: 0
    },
    reducers: {
        add: (state, action) => {
            return {
                num: state.num + action.num
            }
        },
        sub: (state, action) => {
            return {
                num: state.num - action.num
            }
        }
    }
}

app.model(homeModel);
app.model(aboutModel);

const mapStateToProps = (state) => {
    return {
        count: state.home.count
    }
};
const mapDispatchToProps = (dispatch) => {
    return {
        increment() {
            dispatch({type: 'home/add', count: 1});
        },
        decrement() {
            dispatch({type: 'home/sub', count: 1});
        }
    }
};

function Home(props) {
    return (
        <div>
            <p>{props.count}</p>
            <button onClick={() => {
                props.increment()
            }}>+
            </button>
            <button onClick={() => {
                props.decrement()
            }}>-
            </button>
        </div>
    )
}
const AdvHome = connect(mapStateToProps, mapDispatchToProps)(Home);

function App() {
    return (
        <div>
            <AdvHome/>
        </div>
    );
}

app.router(() => <App/>);
app.start('#root');

三、总结

通过本文的学习,您可以掌握以下知识点:

    1. 了解『Dva』的核心
    1. 了解如何定义 Model
    1. 了解如何注册 Model
    1. 了解如何连接 Model
    1. 了解如何使用 Model

🐤如果您觉得本文对您有所帮助,欢迎点赞、收藏或分享,您的支持是我创作的最大动力!

这篇文章的内容就介绍到这里,期待我们下次的相遇。感谢您花时间阅读,如果有任何问题或想法,欢迎在评论区留言。

我正在参与2024腾讯技术创作特训营第五期有奖征文,快来和我瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、管理数据
    • 1.定义 Model
      • 2.注册 Model
        • 3.连接 Model
          • 4.使用 Model
            • 5.回顾
            • 三、总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档