Ant-Design的树形控件Tree的使用心得

树形控件在需要层级展示的时候有很大的用处,如组织架构、国家地区等。这里记录的使用是在关于角色权限的展示。
需求是这样的:不同的角色会有不同的权限,切换角色的时候需要准确的显示该角色拥有的权限,且有相关权限的用户可对角色权限进行修改,即增加或删除用户的某一权限。
一开始看到原型的时候,想到Ant Design有的树形控件,感觉上比较简单。直到真正开始写代码的时候,发现好像并没有想象中那么简单。下面开始摸索之路。

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

const treeData = [
  {
    title: 'parent 1',
    key: '0-0',
    value: false,
    children: [
      {
        title: 'parent 1-0',
        key: '0-0-0',
        value: true,
      },
      {
        title: 'parent 1-1',
        key: '0-0-1',
        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>;
  });

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

前面说到,后端返回的每个权限里还有一个值是用来判断当前权限是否被勾选。相当于是树形控件默认选中的节点。我这里的做法是先依次遍历每个节点,然后取出节点的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;
}

以上这些准备工作其实并不难,思路上也是很容易想到的,难的是如何在切换角色的时候让树形组件准确的显示不同角色所对应的权限。
根据受控组件跟非受控组件两种组件,树形控件的默认选中的节点的写法不同,下面依次来说一下。(我们假设默认节点值放在defaultCheckedKeys这个数组里面)

  1. 受控组件
    受控组件的值的获取以及值的改变都是依赖state。树形组件如果按照受控组件的方法来写的话,使用checkedKeys属性来确定默认节点。
    部分相关代码如下:(这里的demo数据是写死在代码里的treeData数组)
state = {
  defaultCheckedKeys: []
}

componentDidMount() {
  this.setState({
    defaultCheckedKeys: this.getCheckedKeys(treeData)
  })
}

因为受控组件是依赖于state的,所以将defaultCheckedKeys作为state的属性之一,然后在组件挂载之后,将被勾选的数组赋值给defaultCheckedKeys。然后是tree组件的代码:

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

可以看到checkedKeys属性的值是this.state.defaultCheckedKeys,然后当勾选或取消勾选树节点的时候,会触发onCheck事件。代码如下:

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

在这里,通过onCheck事件触发state里的defaultCheckedKeys的值,顺便打印一下checkedKeys的值,也更加方便理解。


默认勾选的是parent1-0(对应的key为0-0-0),然后取消该项的勾选,这时没有任何节点被选中,checkedKeys自然是一个空数组,然后我们再把parent1-0这一项勾选上,checkedKeys就会变成一个拥有“0-0-0“这个元素的数组。
从实现上来看这个方法似乎没有什么问题,但是我们的数据是从后端请求获取的,在render方法里面是不允许使用setState方法的,而在componentDidMount这个生命周期函数里还无法获取到接口返回的数据。所以,采用了非受控组件的方法。
在非受控组件里,树形控件使用defaultCheckedKeys属性来确定默认选中的节点。在受控组件里,defaultCheckedKeys数组的值是依赖于state的,而在非受控组件里,仅作为render里面的一个变量即可。

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

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

而在onCheck函数里,只需要把获取到的值赋值给保存该角色的权限的数组即可。
上面这样写还有一个问题:需求里是不同角色切换的时候,要显示对应的权限树,也就是说,角色切换的时候树是重新渲染的。但在上面的代码中,并不会在角色切换的时候重新渲染。为了解决这个问题,我们给这个树形控件再加一个属性:key。因为角色列表是一个数组,所以可以把key的值跟数组下标对应起来,这样每次进行角色切换的时候,key的值也会发生变化,树形控件也会这个key值的变化而重新渲染。
以上,便是这篇文章的所有内容了。

评论

发表回复

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

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理