创建amis组件

我们前面已经安装好了amis,并且copy官方Demo代码生成了一个react使用amis页面。但是这样是不方便的,我们每一个amis schema都需要重复做这样的页面是不合适的。考虑我们可以读取本地json文档应该设计一个amis组件这个组件可以获取当前urlpath最后一层文件名,根据文件名加载本地json文档做为组件schema
这样做的好处是,运营时候可以交给不懂编程运营人员直接修改修改后的json格式schema上传后就可以了。这样,程序修改,不需要重新编译程序

Hook 方式挂载事件

创建组件,我们采用函数方式:

const AmisComponent: React.FC = () => {
}

这种方式,因为没有采用class继承没有this,没有生命周期函数比如componentDidMount(),componentDidUpdate(),componentWillUnmount()等等。取而代之的是useEffectuseState。

useState

在使用class的时候,组件的数据通过this.state = {},来实现,但是在hook我们采用的方式就是使用useState这个hook示例如下

import { useEffect, useState } from 'react';
const [count, setValue] = useState<number&gt;(0);
setValue(3);

这个代码翻译class方式如下

this.state = {count:0};
this.setState({count:3});

useEffect

useEffect的原型如下

function useEffect(effect: EffectCallback, deps?: DependencyList): void;

总共两个参数,一个是Effect影响函数,一个是依赖列表。影响函数好理解就是我们希望对本组件进行影响的操作
依赖列表一个数组,一个变量名称组成的数组,是触发影响函数的依赖变量。影响函数外面的任何可能改变函数运行结果变量都可以写入依赖列表里面去。例如

  //代码A
  let a=0,b=0,c=0;
  useEffect(() =&gt; {
    console.log(a,b,c);
    },[])

上面这段代码里面,a,b,c三个变量可能改变函数的运行,所以都可以写到依赖列表里面去:

  //代码B
  let a=0,b=0,c=0;
  useEffect(() =&gt; {
    console.log(a,b,c);
    },[a,b,c])

上面两段代码A和B,由于A里面的代码依赖列表是空白的,所以useEffect里面的影响函数指执行一次。代码B里面的依赖列表是a,b,c只要a,b,c有变化,影响函数就会执行

useEffect与class时代生命周期

基于上面对useEffect的理解我们可以整理出它和class时代生命周期函数的对应关系:
1、componentDidMount() 对应如下代码:

useEffect(()=&gt;{},[]);//组件渲染后,执行一次。因为依赖列表里面是空白

2、componentWillUnmount(),用hook来代替,需要去return一个callback(回调函数),如下面的形式所示

 useEffect(() =&gt; {
    return () =&gt; {
      //todoclear something
    };
  }, []);

3、componentDidUpdate(),用一个特殊变量的去触发hook,如下面所示,count指的就是这个特殊变量,该hook触发,只会是count的值改变时,这个也相当vue里面的watch,可以监控某个变量的变化:

useEffect(() =&gt; { },[count])

组件加载json文件

有了以上的知识基础,我们可以开始实现我们的功能。由于我们设计组件的初衷就要组件可以根据location中的path自动加载,对应的json,我们需要解析location最后一层,并且初始化myschema(用来保存json内容变量)变量,由于我们需要把该变量丢给render渲染函数,根据amis里面schema类型定义,我们发现有一个type属性是非空的,所有我们useState的时候,初始变量必须是{type:‘page’},实在懒得写,也可以给一个{}空白对象作为初始值,否则初次渲染的时候就会报错:TypeError: Cannot read propertymobile’ of undefined。这个是amis渲染器没有undefined判断原因

 //获取path路径中的json文件名
  const location = useLocation();
  const schemaFile = location.pathname.substring(location.pathname.lastIndexOf('/') + 1);
  //初始化Schema
  const [myschema, setValue] = useState<Schema>({ type: 'page', title: '', body: '' });

加载schema文件时,我们采用umirequest库,改库返回的是一个Promise,在useEffect也可以用.then来实现赋值,但是这种很破坏结构新的回调函数方式我比较不喜欢。通常解决办法是用async/await,由于我们采用函数式,React.FC如果前面async,会有类型错误提醒typescript问题),为了确保函数类型一致,我们采用立即执行函数IIFE。因此useEffect被设计成这样:

useEffect(() => {   
    (async () => {
      const schema = await getLocalSchema(schemaFile, initialState?.ver);
      if (isMounted) setValue(schema);
    })();//IIFE
  }, [schemaFile]);//我们希望组件会根据当前location里面的path的变化重新加载不同的json文件,所以这个地方的依赖列表不能空

提醒注意,由于我们需要在用户点击不同菜单项的时候加载不一样的json文件,所以我们需要监控schemaFile的变化,必须要把它列入依赖列表。

综上,组件完整代码如下:

/* eslint-disable react-hooks/exhaustive-deps */
// import axios from 'axios';
import copy from 'copy-to-clipboard';
import './cxd.css';
import { render as renderAmis } from 'amis';
import { ToastComponent, AlertComponent, toast } from 'amis-ui';
import { useLocation } from 'react-router';
import { useModel } from 'umi';
import { getLocalSchema } from '@/services/ant-design-pro/api';
import { useEffect, useState } from 'react';
import type { Schema } from 'amis';
import request, { amisRequest } from '@/utils/request';
import remoteRequest from '@/utils/request';
//import request, { localRequest } from '@/utils/request';
const AmisComponent: React.FC = () => {
  //获取path路径中的json文件名
  const location = useLocation();
  const schemaFile = location.pathname.substring(location.pathname.lastIndexOf('/') + 1);
  //获取版本号
  const { initialState } = useModel('@@initialState');
  //初始化Schema
  const [myschema, setValue] = useState<Schema>({ type: 'page', title: '', body: '' });
  //读取本地json文件mySchema变量
  useEffect(() => {
    //https://zhuanlan.zhihu.com/p/454841748
    //设置isMounted是为了解决react内存泄露警告问题
    let isMounted = true;
    (async () => {
      const schema = await getLocalSchema(schemaFile, initialState?.ver);
      if (isMounted) setValue(schema);
    })();
    return () => {
      isMounted = false;
    };
  }, [schemaFile]);
  const theme = 'cxd';
  const locale = 'zh-CN';
  // 请勿使用 React.StrictMode,目前还不支持
  return (
    <div>
      <ToastComponent theme={theme} key="toast" position={'top-right'} locale={locale} />
      <AlertComponent theme={theme} key="alert" locale={locale} />
      {
        renderAmis(
        myschema,//这个就是json文件内容     
        {
          // 下面三个接口必须实现
          fetcher: ({
            url, // 接口地址
            method, // 请求方法 getpostputdelete
            data, // 请求数据
            responseType,
            config, // 其他配置
            headers, // 请求
          }: any) => {
            // eslint-disable-next-line no-param-reassign
            config = config || {};
            config.withCredentials = true;
            // eslint-disable-next-line @typescript-eslint/no-unused-expressions
            responseType &amp;&amp; (config.responseType = responseType);

            if (config.cancelExecutor) {
              request.CancelToken = config.cancelExecutor;
              //config.cancelToken = new (axios as any).CancelToken(config.cancelExecutor);
            }

            config.headers = headers || {};

            if (method !== 'post' &amp;&amp; method !== 'put' &amp;&amp; method !== 'patch') {
              if (data) {
                config.params = data;
              }

              return amisRequest(url, method, { ...data, ...config }); // (axios as any)[method](url, config);
            } else if (data &amp;& data instanceof FormData) {
              config.headers = config.headers || {};
              config.headers['Content-Type'] = 'multipart/form-data';
            } else if (
              data &&
              typeof data !== 'string' &&
              !(data instanceof Blob) &&
              !(data instanceof ArrayBuffer)
            ) {
              // eslint-disable-next-line no-param-reassign
              data = JSON.stringify(data);
              config.headers = config.headers || {};
              config.headers['Content-Type'] = 'application/json';
            }
            //return (axios as any)[method](url, data, config);
            return amisRequest(url, method, { ...data, ...config });
          },
          isCancel: (value: any) => remoteRequest.isCancel(value), //isCancel(value),
          copy: (content) => {
            copy(content);
            toast.success('内容复制到粘贴板');
          },
          theme,

         
        },
      )}
    </div>
  );
};
export default AmisComponent;

修改路由

有了组件,我们可以添加页面了:src/pages里面添加amispage,并完成代码如下:

import AmisComponent from '@/components/Amis';
export default AmisComponent;

这个页面承载了我们的设计思想就是根据不同的path加载不同的json文件,那么这个页面就会频繁的被调用,它在路由里面是需要设置参数的,这个参数就是我们的json文件名。在config/config.ts的路由数组里面添加如下代码

{
      path: '/amispage/:f',
      name: 'amispage',
      icon: 'smile',
      component: './amispage',
},

我们在前面服务器加载菜单的时候,为了实现菜单图标,有做过一个组件LocalMenu实现菜单数据遍历。我们可以改造一下,对菜单数据里面的每个单项加一指向amispage的前缀,这样运营人员设计菜单的json文档的时候,就可以在path里面直接书写文件名了。src/components/LocalMenu/index.tsx代码修改如下:

const loopMenuItem = (menus: any[]): MenuDataItem[] =>
  menus.map(({ icon, routes, filename, path, ...item }) => {
    let newPath = path;
    if (path) {
      newPath = `/amispage/${path}`;
    }
    return {
      ...item,
      path: newPath,
      icon: icon && IconMap[icon as string],
      children: routes && loopMenuItem(routes),
    };
  });

这样,我们的运营人员就可以自由的设计如下的菜单json,其中每个path都对应一个在public/json文件夹里面的json文件,比如path:index对应public/json/index.json,菜单json示例如下:

[
  {
    "path": "/",
    "name": "表的crud",
    "locale":false,
    "icon": "smile",  
    "routes": [      
      {
        "path": "index",
        "name": "crud基本操作"        
      },
      {
        "path": "crudNew",
        "name": "crud新增操作"
      },
      {
        "path": "crudDelete",
        "name": "crud删除操作"
      }      
    ]
  },
  {
    "path": "echart",
    "locale":false,
    "icon": "heart",
    "name": "其它示例"
  }
]

最终界面呈现如下:(每个页面对应的schema内容详见下一篇amis组件的再改造)
在这里插入图片描述

原文地址:https://blog.csdn.net/zzcaism/article/details/127655300

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_49104.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注