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

14次阅读

共计 2806 个字符,预计需要花费 8 分钟才能阅读完成。

树形控件在需要层级展示的时候有很大的用处,如组织架构、国家地区等。这里记录的使用是在关于角色权限的展示。
需求是这样的:不同的角色会有不同的权限,切换角色的时候需要准确的显示该角色拥有的权限,且有相关权限的用户可对角色权限进行修改,即增加或删除用户的某一权限。
一开始看到原型的时候,想到 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 值的变化而重新渲染。
以上,便是这篇文章的所有内容了。

正文完
 0