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 数据流。熟练的掌握这些概念,会够大幅度的提升你的开发体验。因为不管再复杂的页面,都可以拆成这样一传一传的数据流。可以说掌握这些概念,就已经可以让你很好的编写前端逻辑了。



源码归档

目录
相关文章
|
16天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
38 3
|
3月前
|
前端开发 JavaScript API
掌握React表单管理的高级技巧:探索Hooks和Context API如何协同工作以简化状态管理与组件通信
【8月更文挑战第31天】在React中管理复杂的表单状态曾是一大挑战,传统上我们可能会依赖如Redux等状态管理库。然而,React Hooks和Context API的引入提供了一种更简洁高效的解决方案。本文将详细介绍如何利用Hooks和Context API来优化React应用中的表单状态管理,通过自定义Hook `useForm` 和 `FormContext` 实现状态的轻松共享与更新,使代码更清晰且易于维护,为开发者带来更高效的开发体验。
44 0
|
4月前
|
开发框架 JavaScript 前端开发
基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式
基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式
|
3月前
|
JavaScript 前端开发 搜索推荐
揭秘 Vue 3 的 Teleport 特性,让你实现跨组件传输内容,使得开发变得更加得心应手!!
揭秘 Vue 3 的 Teleport 特性,让你实现跨组件传输内容,使得开发变得更加得心应手!!
|
5月前
|
存储 前端开发 JavaScript
在React中有效地管理组件之间的通信和数据流
在React中有效地管理组件之间的通信和数据流
|
4月前
|
JavaScript 前端开发
前端 JS 经典:单向数据流
前端 JS 经典:单向数据流
31 0
|
6月前
|
JavaScript 前端开发 算法
Vue.js的单向数据流:让你的应用更清晰、更可控
Vue.js的单向数据流:让你的应用更清晰、更可控
|
6月前
|
前端开发 JavaScript API
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
React 生态系统:路由、状态管理、调试、测试、组件库、文档……
113 0
05-流式操作:使用 Flux 和 Mono 构建响应式数据流(上)
05-流式操作:使用 Flux 和 Mono 构建响应式数据流
279 0
05-流式操作:使用 Flux 和 Mono 构建响应式数据流(下)
05-流式操作:使用 Flux 和 Mono 构建响应式数据流
731 0