012 Umi 的数据流最佳实践(1/2) - 页面级的 hooks 数据流

简介: 012 Umi 的数据流最佳实践(1/2) - 页面级的 hooks 数据流

image.png


页面级的 hooks 数据流

虽然我们假定当你阅读本文档的时候,你已经了解过 react hooks 相关的知识了,但是本次的文档希望面临的受众更广一些,所以依旧会很详细的讲解几个常用的 hooks。如果你无法很轻易的掌握本文档的内容,那你可以通过搜索阅读其他的文档来强化这些概念。此处不给出推荐链接,推荐进行内部的初级开发认证。


本文档假定你是按照我们课程设计的顺序进行阅读的,即表示到达这里,你已经熟练掌握了 dva 的相关概念。因此我会直接使用 dva 的概念来类比介绍 react hooks 的概念。但事实上 react hooks 的概念是要比 dva 来的更容易掌握的。这么做的目的也是为了让你再次熟悉 dva。

为什么都不是最佳实践了,我还要一直提 dva


useState

我们经常在项目中看到如下的代码:

import { useState } from 'react';
const [state, setstate] = useState(initialState)
复制代码

这里我们以 dva 的概念类比理解:


initialState 与 init state

// initialState
state: {
  name: 'learn umi',
},
// 如果我们想像上面这样定义和初始化变量,我们可以使用如下 hooks
const [name, setName] = useState('learn umi');
复制代码


name 与 select state

在我们定义好变量之后,在后续的任何时候取值,都能取到最新的数据。

// hooks
const [name, setName] = useState('learn umi');
const newName = name + 1;
// dva 中
const { name } = yield select(_=>_.global);
const newName = name + 1;
复制代码


setName 与 reducers save

// hooks
const [name, setName] = useState('learn umi');
setName('Umi 入门教程')
// dva 中
yield put({
  type: 'save',
  payload: { name: 'umi 入门教程' },
});
复制代码

注意:调用 setName 是要注意深浅拷贝的问题,你可以简单的记忆,当你 set 的数据是一个数组或者对象时,记得使用解构(...)返回一个新的对象。


async/await 与 effects

const delay = () => new Promise(resolve => {
  setTimeout(resolve, 1000);
})
// hooks
const [list, setList] = useState(['step1', 'step2', 'step3', 'step4']);
const deleteItem = async item => {
  await delay()
  list.splice(list.findIndex(e => e === item), 1)
  setList([...list])
}
// dva 中
{
  state: {
    name: 'learn umi',
    list: ['step1','step2','step3','step4']
  },
  effects: {
    *deleteItem({ payload }, { call, put,select }) {
        const { list } = yield select(_=>_.global);
        yield call(delay)
        list.splice(list.findIndex(e => e === payload), 1)
        yield put({
          type: 'save',
          payload: { list },
        });
      },
  },
}
复制代码


整理

新建文件 src/pages/useState.tsx

import React, { useState } from "react";
import { Link } from "umi";
export default function List() {
  const [list, setList] = useState(["step1", "step2", "step3", "step4"]);
  const [name, setName] = useState("learn umi");
  const delay = () =>
    new Promise((resolve) => {
      setTimeout(resolve, 1000);
    });
  const deleteItem = async (item: any) => {
    await delay();
    list.splice(
      list.findIndex((e) => e === item),
      1
    );
    setList([...list]);
  };
  return (
    <div>
      <h1>{name}</h1>
      <button
        onClick={() => {
          setName("Umi 入门教程");
        }}
      >
        Click Me!
      </button>
      <Link to="/">Go to index page</Link>
      <ul>
        {list.map((i) => (
          <li key={i}>
            <button
              onClick={() => {
                deleteItem(i);
              }}
            >
              删除{i}{" "}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
复制代码


访问 http://localhost:8888/useState,因为我们之前给 render 加了 3 秒延时渲染,所以你将需要等待 3 秒才能看到页面,点击 Click Me! 页面上的文字,从 learn umi 变为 Umi 入门教程。点击下方的按钮,被点击的按钮,将会在延迟1秒之后,从页面中被移除。


到这里,我们就将 setState 的概念讲解完毕了,你很容易发现,在 react hooks 中就一行代码的逻辑,在 dva 中却需要跨越两个文件写比较多的模版代码。这也是我们现在不推荐在项目中重度使用 dva 的主要原因。

由于 react hooks 无法在 组件之外使用,因此我们依旧需要保留 dva 用作一些全局的数据管理和在一些组件之外操作数据的场景。


useEffect

在 dva 中也有一个同名的概念 effects,之所以没有拿它和 useEffect 类比,是因为他们不太像是一个东西,放在一起反而容易混乱。比起 effects,useEffect 更像 dva 中的 subscription。你会发现在前面的概念和实战中,我们都没有提到这个概念。因为在实际的项目中,我们发现使用 useEffect 会比 subscription 逻辑更加清晰。如果你对 subscription 感兴趣,你可以查看 dva 的文档了解更多。


useEffect 接收一个包含命令式、且可能有副作用代码的函数。

useEffect(didUpdate);
复制代码


当 useEffect 只接收一个函数时,表示函数在每一次页面渲染完成之后调用。

在项目中我们常用的方法是当达成某一个条件之后,再执行某个函数这样的逻辑。因此我们在第二参数传入一个数组,数组里面是我们期望这些值变化的时候,触发函数调用。

比如,当 name 值发生变化时调用:

import { useState, useEffect } from 'react';
const [name, setName] = useState('learn umi');
useEffect(() => {
  console.log('name value change!');
}, [name])
复制代码

注意:name 值发生变化的时机包括 name 的初始化数据。即此时的 useEffect 的函数至少会被调用一次。


当你希望页面初始化的时候,调用 effect 时,你可以在第二参数传入一个空数组([])。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。

import { useState, useEffect } from 'react';
useEffect(() => {
  console.log('page init');
}, [])
复制代码


当你需要在组件中绑定监听或者定时器时,你也可以在此时机中执行。但是请一定要记得在组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,只需要在 effect 函数中,返回清除函数即可。

比如,清除计时器 ID:

import { useState, useEffect } from 'react';
useEffect(() => {
  console.log('page init');
  const timekey = setInterval(() => {
    console.log('每秒调用一次');
  }, 1000);
  return () => {
    // 清除
    clearInterval(timekey);
  };
}, [])
复制代码


比如,清除订阅:

import { useState, useEffect } from 'react';
useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除订阅
    subscription.unsubscribe();
  };
});
复制代码



开发技巧

一般情况下 name 都是能取到最新的值的,但如果你是在一个比较复杂的环境中使用它,并且你无法保证它一定是最新值的时候,在使用 setName 的时候,可以使用函数式的设置方式,如 setName(name=>name+1) 该用法会先接收最新的值,可以让他脱离外层的引用,这在 useEffect 回调中频繁修改数值的时候,会非常好用。


比如,我们每秒修改一次数值:

import { useState, useEffect } from 'react';
const [count, setCount] = useState(0);
useEffect(() => {
  console.log('page init');
  const timekey = setInterval(() => {
    console.log('每秒调用一次');
    setCount(count=>count+1);
  }, 1000);
  return () => {
    // 清除
    clearInterval(timekey);
  };
}, [])
复制代码


整理

新建文件 src/pages/useEffect.tsx

import React, { useState, useEffect } from "react";
export default function List() {
  const [name, setName] = useState("learn umi");
  const [effect, setEffect] = useState("no");
  const [count, setCount] = useState(0);
  useEffect(() => {
    setEffect(name === "umi 入门教程" ? "yes" : "no");
  }, [name]);
  useEffect(() => {
    console.log("page init");
    const timekey = setInterval(() => {
      console.log("每秒调用一次");
      setCount((count) => count + 1);
    }, 1000);
    return () => {
      // 清除
      clearInterval(timekey);
    };
  }, []);
  return (
    <div>
      <h1>count:{count}</h1>
      <h1>{name}</h1>
      <h1>name change:{effect}</h1>
      <button
        onClick={() => {
          setName("umi 入门教程");
        }}
      >
        Click Me!
      </button>
    </div>
  );
}
复制代码

访问 http://localhost:8888/useEffect,你将在页面上看到一个自动累加的计数器,点击 Click Me! 页面上的文字,从 learn umi 变为 Umi 入门教程name change: 也从 no 变成 yes



总结

在开发中我们最常用的 react 官方 hooks 就是 useState 和 useEffect。对于官方的其他 hooks,对我们的开发也有很大的帮助,当你熟练掌握 useState 和 useEffect 之后,你应该去官网全面的学习全部的 react hooks


到目前位置,我们详细的讲解了我们在项目中会使用到的 dva 数据流和页面级的 hooks 数据流。熟练的掌握这些概念,会够大幅度的提升你的开发体验。因为不管再复杂的页面,都可以拆成这样一传一传的数据流。可以说掌握这些概念,就已经可以让你很好的编写前端逻辑了。



源码归档

目录
相关文章
|
4天前
|
JavaScript 前端开发 开发者
Vue的事件处理机制提供了灵活且强大的方式来响应用户的操作和组件间的通信
【5月更文挑战第16天】Vue事件处理包括v-on(@)指令用于绑定事件监听器,如示例中的按钮点击事件。事件修饰符如.stop和.prevent简化逻辑,如阻止表单默认提交。自定义事件允许组件间通信,子组件通过$emit触发事件,父组件用v-on监听并响应。理解这些机制有助于掌握Vue应用的事件控制。
18 4
|
6天前
|
JavaScript 前端开发 算法
Vue.js的单向数据流:让你的应用更清晰、更可控
Vue.js的单向数据流:让你的应用更清晰、更可控
|
6天前
|
前端开发 JavaScript API
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
46 0
|
8月前
|
JavaScript
【Vue】模板语法,事件处理器及综合案例、自定义组件、组件通信
组件(Component)是Vue最强大的功能之一组件可以扩展HTML元素,封装可重用的代码组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树。
|
9月前
|
JavaScript 前端开发 中间件
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理7
42 0
|
9月前
|
JavaScript 前端开发 中间件
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理10
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理10
34 0
|
9月前
|
JavaScript 前端开发 中间件
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理11
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理11
38 0
|
9月前
|
JavaScript 前端开发 中间件
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理2
前端学习笔记202307学习笔记第六十三天-redux单项数据流和中间件原理2
45 0
|
编解码 算法 前端开发
以寡治众各个击破,超大文件分片上传之构建基于Vue.js3.0+Ant-desgin+Tornado6纯异步IO高效写入服务
分治算法是一种很古老但很务实的方法。本意即使将一个较大的整体打碎分成小的局部,这样每个小的局部都不足以对抗大的整体。战国时期,秦国破坏合纵的连横即是一种分而治之的手段;十九世纪,比利时殖民者占领卢旺达, 将卢旺达的种族分为胡图族与图西族,以图进行分裂控制,莫不如是。
以寡治众各个击破,超大文件分片上传之构建基于Vue.js3.0+Ant-desgin+Tornado6纯异步IO高效写入服务
|
存储 前端开发 JavaScript
为什么我使用 Umi 的 model 简易数据流管理插件
Umi 是一款企业级的 React 前端应用框架,云巧产业数字组件中心推荐使用基于 Umi 的 Koi 框架统一前端应用研发流程,支撑前端项目从研发、联调到上线、发布的全流程。 本文假设您正在或计划使用 Umi 或 Koi 作为底层框架支撑前端应用的开发,并且对 Umi 有一定的了解。 ## 数据治理的原则 React 的核心特征是“数据驱动视图”,用公式表达即 `UI = render(d