clean-js | 在hooks的时代下,使用class管理你的状态

383 阅读3分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章,点击查看活动详情

贴上源码 👉🏻 github.com/lulusir/cle…

期待宝子们的star⭐️

先上例子🌰

✨ Counter ✨

需求: 展示点击次数,每次点击都会更新

首先我们定义需要展示的状态和点击方法

import React from 'react';
import { usePresenter } from '@clean-js/react-presenter';
import { Presenter } from '@clean-js/presenter';

interface IViewState {
  count: number
}

class CounterPresenter extends Presenter<IViewState> {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
  }
  increment = () => {
    this.setState((s) => {
      s.count += 1
    });
  }

  decrement = () =>{
     this.setState((s) => {
      s.count -= 1
    });
  }
}

接下来绑定到组件中

const Counter = () => {
  const { presenter, state } = usePresenter(CounterPresenter);
  return (
    <div>
      <p>{state.count}</p>
      <button
        onClick={presenter.increment}
      >
        increment
      </button>
      <button
        onClick={presenter.decrement}
      >
        decrement
      </button>
    </div>
  );
};

export default Counter;

在线地址:链接https://lulusir.github.io/clean-js/demos/counter

✨ TodoList ✨

接下来又是我们的老演员Todolist了

需求:

  1. 需要3个list来分别展示 Todo列表,分别是:待完成列表,已完成列表,删除列表
  2. 需要一个输入框和按钮用来添加Todo
  3. 需要一个按钮来随机完成Todo(打个响指✌🏻)
声明Todo的Entity接口
export interface Todo {
  status: 'done' | 'default' | 'delete';

  id: number;

  content: string;
}

.

实现Presenter,提供数据和方法
import { Presenter, injectable } from '@clean-js/presenter';
import { Todo } from './todo.entity';
/**
 * 定义页面需要展示的内容
 * loading
 * 三种类型的Todos
 */

interface IViewState {
  data: Todo[];
}


export class TodoPresenter extends Presenter<IViewState> {
  constructor() {
    super();
    this.state = { data: [] };
  }

  // 添加一个todo,
  add(content: string) {
    this.setState((s) => {
      s.data.push({ content, id: Math.random(), status: 'default' });
    });
  }

  initTotoList() {
    for (let i = 0; i < 3; i++) {
      const content = `content ${i}`;
      this.add(content);
    }
  }

  finish(id: number) {
    this.setState((s) => {
      const t = s.data.find((v) => v.id === id);
      if (t) {
        if (t.status === 'default') {
          t.status = 'done';
        } else if (t.status === 'done') {
          t.status = 'delete';
        }
      }
    });
  }

  /**
   * 打个响指 完成一半
   */
  snapFingers() {
    this.defaultList().forEach((v, i) => {
      if (i % 2 === 0) {
        this.finish(v.id);
      }
    });
  }

  defaultList() {
    return this.state.data.filter((v) => v.status === 'default');
  }

  doneList() {
    return this.state.data.filter((v) => v.status === 'done');
  }

  deleteList() {
    return this.state.data.filter((v) => v.status === 'delete');
  }
}
编写界面

在React的函数组件中, 可以用usePresenter来获取TodoPresenter实例

接下来就是把方法和需要渲染的数据绑定到组件中,一个简单的Todolist就完成了👍🏻

import React, { useEffect, useRef } from 'react';
import { Spin, List, Button, Space } from 'antd';
import { usePresenter } from '@clean-js/react-presenter';
import { TodoPresenter } from './index.presenter';

const Index = () => {
  const { presenter } = usePresenter(TodoPresenter);

  const refInput = useRef<HTMLInputElement>(null);

  useEffect(() => {
    presenter.initTotoList();
  }, []);

  return (
    <Spin spinning={false}>
      <div style={{ marginBottom: '20px' }}>
        <input type="text" ref={refInput} />
        <Button
          style={{ marginLeft: '20px' }}
          onClick={() => {
            if (refInput.current) {
              const content = refInput.current.value;
              if (content) {
                presenter.add(content);
              }
            }
          }}
        >
          add todo
        </Button>
        <Button
          style={{ marginLeft: '20px' }}
          onClick={() => {
            presenter.snapFingers();
          }}
        >
          Snap your Fingers
        </Button>
      </div>

      <Space direction="vertical" size="middle" style={{ display: 'flex' }}>
        <List
          size="small"
          header={'Todo list'}
          bordered
          dataSource={presenter.defaultList()}
          renderItem={(item) => (
            <List.Item>
              {item.content}
              <Button
                style={{ marginLeft: '20px' }}
                onClick={() => {
                  presenter.finish(item.id);
                }}
              >
                finish
              </Button>
            </List.Item>
          )}
        />
        <List
          size="small"
          header={'Done list'}
          bordered
          dataSource={presenter.doneList()}
          renderItem={(item) => (
            <List.Item>
              {item.content}
              <Button
                danger
                style={{ marginLeft: '20px' }}
                onClick={() => {
                  presenter.finish(item.id);
                }}
              >
                delete
              </Button>
            </List.Item>
          )}
        />
        <List
          size="small"
          header={'Delete list'}
          bordered
          dataSource={presenter.deleteList()}
          renderItem={(item) => (
            <List.Item>
              <del>{item.content}</del>
            </List.Item>
          )}
        />
      </Space>
    </Spin>
  );
};

export default () => <Index />;

Todo 在线链接

✨ API ✨

Presenter

Presenter是一个状态管理基类,提供了stateState方法和state属性让你设置,修改状态。

  1. 声明State的接口
  2. 继承这个Presenter这个基类,并初始化状态

Method

参数说明类型默认值
stategetter 返回 stateIViewState
setState设置state,基于immer,在setSatate可以用immer的语法来修改状态IViewState
import { Presenter, injectable } from '@clean-js/presenter';
import { Todo } from './todo.entity';
/**
 * 定义页面需要展示的内容
 * loading
 * 三种类型的Todos
 */

interface IViewState {
  data: Todo[];
}


class TodoPresenter extends Presenter<IViewState> {
  constructor() {
    super();
    this.state = { data: [] };
  }
.
.
.

✨ View adaptor ✨

视图适配器,在React/Vue 组件中,通过usePresenter来获取Presenter实例

使用如下:

  • 使用 usePresenter hook
  • 注入 Presenter 类
  • 获取 presenter 实例, state 对象
  • 在 presenter 使用 setState 方法可以更新 state,并且默认会更新视图
import { usePresenter } from '@clean-js/react-presenter';
.
.
.
const Counter = () => {
  const { presenter, state } = usePresenter(CounterPresenter);
  return (
    <div>
      <p>{state.count}</p>
      <button
        onClick={presenter.increment}
      >
        increment
      </button>
      <button
        onClick={presenter.decrement}
      >
        decrement
      </button>
    </div>
  );
};

✨ 调试 ✨

方式1 👉 redux-devtool

  1. 安装chrome插件redux-devtool
  2. 开启devtool
import { entry } from '@clean-js/presenter';

if (isLocal) {
  entry.showDevtool() // 开启devtool
}

方式2 👉🏻 console.log

if (isLocal) {
  entry.showLog() // 开启log,通过console.log输出日志,可以用于小程序环境
}

下一篇会接着晚上TodoList,添加更多的功能,敬请期待

贴上源码 👉🏻 github.com/lulusir/cle…

期待宝子们的star⭐️

整洁架构篇:juejin.cn/post/714092…


其他文章
什么?在React中也可以使用vue响应式状态管理
clean-js | 自从写了这个辅助库,我已经很久没有加过班了…
clean-js | 在hooks的时代下,使用class管理你的状态
clean-js | 手把手教你写一个羊了个羊麻将版
写给前端的数据库入门 | 序
写给前端的数据库入门 | docker & 数据库
有没有一种可能,你从来都没有真正理解async
三分钟实现前端写JAVA这件事——装环境
三分钟实现前端写JAVA这件事——VS code