1

需求是这样的:有一个角色设置模块,左侧为角色列表,右侧为权限树。不同的角色会有不同的权限,切换角色的时候需要准确的显示该角色拥有的权限,且有相关权限的用户可对角色权限进行修改,即增加或删除用户的某一权限。

首先,角色所拥有的权限树是通过请求接口返回的对象数组的数据,每一个权限拥有label、key、value和children。label、key、children对应tree组件里面的title、key和children,value是一个布尔值,用来判断当前权限是否被勾选(true为被勾选)。大致数据结构如下:

export const treeData = [
  {
    title: "全局权限",
    key: "0",
    value: false,
    children: [
      {
        title: "查看手机号",
        key: "1",
        value: false
      }
    ]
  },
  {
    title: "账号管理",
    key: "2",
    value: false,
    children: [
      {
        title: "新增账号",
        key: "3",
        value: true
      },
      {
        title: "编辑账号",
        key: "4",
        value: false
      },
      {
        title: "删除账号",
        key: "5",
        value: false
      }
    ]
  }
]

在拿到数据之后,需要把数据转换成树形控件,因为存在子节点的渲染,所以自然而然也会想到递归的方法,这里采用的是Ant Design文档里提供的方法:

// 组件树形控件 子节点渲染
renderTreeNodes = data =>
  data.map(item => {
    if (item.children) {
      return (
        <TreeNode title={item.title} key={item.key}>
          {this.renderTreeNodes(item.children)}
        </TreeNode>
      );
    }
    return <TreeNode title={item.title} key={item.key}></TreeNode>;
  });

至此,后端返回的数据可以以树形控件的形式展示:

clipboard.png

前面说到,后端返回的每个权限里还有一个值是用来判断当前权限是否被勾选。相当于是树形控件默认选中的节点。我这里的做法是先依次遍历每个节点,然后取出节点的value是true的值,返回一个数组,代码如下:

// 权限树获取已勾选的节点的key值(value为true)
getCheckedKeys = (data) => {
  let arr = [];
  for (let i = 0;i < data.length;i++) {
    if (data[i].value) {
      arr.push(data[i].key)
    }
    if (data[i].children) {
      let res = this.getCheckedKeys(data[i].children)
      arr = [...arr, ...res]
    }
  }
  return arr;
}

以上,已可以将后端返回的数据以树形结构展示出来,且可以获得被勾选的节点的数组。
下一步工作就是要让默认节点被勾选上,且可以勾选或取消勾选任意节点。
首先想到的是受控组件的写法:
受控组件是依赖于state的,即不管是默认值的赋值还是值的改变都是要通过state的。但由于需求里的数据是从后端请求获取的,在render函数里面是不允许使用setState方法的,而在componentDidMount这个生命周期函数里还无法获取到接口返回的数据。所以只能考虑请求接口之后得到数据再做处理。dispatch这个函数有一个参数是回调函数,我们可以在请求接口的时候把接口返回的数据作为回调函数的参数,这样一来,我们就可以在请求接口的回调函数里拿到我们所想到的数据了,并且可以使用setState方法,render函数代码如下:

render() {
  return (
    <Tree
      checkable
      onCheck={this.onCheck}
      checkedKeys={this.state.defaultCheckedKeys}
    >
      {this.renderTreeNodes(treeData)}
    </Tree>
  );
}

当勾选或取消勾选树节点的时候,会触发onCheck事件。代码如下:

onCheck = (checkedKeys) => {
  this.setState({
    defaultCheckedKeys: checkedKeys
  })
}

除了受控组件的写法,还可以采用非受控组件。不过会存在一定的性能问题,具体的下面看代码再解释。

render() {
  let defaultCheckedKeys = this.getCheckedKeys(treeData);

  return (
    <Tree
      checkable
      onCheck={this.onCheck}
      defaultCheckedKeys={defaultCheckedKeys}
      key={this.treeIndex}
    >
      {this.renderTreeNodes(treeData)}
    </Tree>
  );
}

因为需求里是不同角色切换的时候,要显示对应的权限树,也就是说,角色切换的时候树是重新渲染的。所以在上面的代码中,我们给这个树形控件再加一个属性:key。因为角色列表是一个数组,所以可以把key的值跟数组下标对应起来,这样每次进行角色切换的时候,key的值也会发生变化,树形控件也会这个key值的变化而重新渲染。但是,由于树形控件的重新渲染,如果一旦数据比较庞大的话,可能就会出现性能问题,在角色切换的时候,权限树的显示会有一定的延迟。
相比较而言,不管数据是不是从后端获取的,受控组件的写法更好一点,且不存在性能问题,但是受控组件是依赖于state的,所以值的获取和改变都与state相关,这点需要注意,不然可能就会出现意想不到的问题。
demo链接


Uniquec
22 声望3 粉丝