diff --git a/components/_util/BaseMixin.js b/components/_util/BaseMixin.js
index cdbd3fd87..1897d3cfe 100644
--- a/components/_util/BaseMixin.js
+++ b/components/_util/BaseMixin.js
@@ -1,4 +1,3 @@
-import { getOptionProps } from './props-util'
 
 export default {
   directives: {
@@ -17,9 +16,9 @@ export default {
   methods: {
     setState (state, callback) {
       const newState = typeof state === 'function' ? state(this.$data) : state
-      if (this.getDerivedStateFromProps) {
-        Object.assign(newState, this.getDerivedStateFromProps(getOptionProps(this), this.$data, true) || {})
-      }
+      // if (this.getDerivedStateFromProps) {
+      //   Object.assign(newState, this.getDerivedStateFromProps(getOptionProps(this), { ...this.$data, ...newState }, true) || {})
+      // }
       Object.assign(this.$data, newState)
       this.$nextTick(() => {
         callback && callback()
diff --git a/components/tree-select/__tests__/__snapshots__/demo.test.js.snap b/components/tree-select/__tests__/__snapshots__/demo.test.js.snap
index 1d75453ce..42922c124 100644
--- a/components/tree-select/__tests__/__snapshots__/demo.test.js.snap
+++ b/components/tree-select/__tests__/__snapshots__/demo.test.js.snap
@@ -1,25 +1,19 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`renders ./components/tree-select/demo/basic.md correctly 1`] = `
-<span class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" tabindex="0" class="ant-select-selection
-            ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>
-`;
+exports[`renders ./components/tree-select/demo/basic.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span class="ant-select-selection ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>`;
 
 exports[`renders ./components/tree-select/demo/checkable.md correctly 1`] = `
-<span class="ant-select ant-select-enabled" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" class="ant-select-selection
-            ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li title="Node1" unselectable="unselectable" class="ant-select-selection__choice"><span class="ant-select-selection__choice__remove"><i class="ant-select-remove-icon anticon anticon-close"><svg viewBox="64 64 896 896" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></i></span><span class="ant-select-selection__choice__content">Node1</span></li>
-<li class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input role="textbox" class="ant-select-search__field"><span class="ant-select-search__field__mirror">&nbsp;</span></span></li>
-</div><span class="ant-select-search__field__placeholder" style="display: none;">Please select</span></span>
-</span>
+<span role="combobox" aria-haspopup="listbox" tabindex="-1" class="ant-select ant-select-enabled" style="width: 300px;"><span class="ant-select-selection ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li unselectable="unselectable" role="menuitem" title="Node1" class="ant-select-selection__choice"><span class="ant-select-selection__choice__remove"><i class="ant-select-remove-icon anticon anticon-close"><svg viewBox="64 64 896 896" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></i></span><span class="ant-select-selection__choice__content">Node1</span></li>
+<li class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input type="text" aria-label="filter select" aria-autocomplete="list" aria-multiline="false" class="ant-select-search__field" style="width: 0px;"><span class="ant-select-search__field__mirror">&nbsp;</span></span></li>
+</div>
+</span></span>
 `;
 
 exports[`renders ./components/tree-select/demo/multiple.md correctly 1`] = `
-<span class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" class="ant-select-selection
-            ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input role="textbox" class="ant-select-search__field"><span class="ant-select-search__field__mirror">&nbsp;</span></span></li>
+<span role="combobox" aria-haspopup="listbox" tabindex="-1" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span class="ant-select-selection ant-select-selection--multiple"><div class="ant-select-selection__rendered"><li class="ant-select-search ant-select-search--inline"><span class="ant-select-search__field__wrap"><input type="text" aria-label="filter select" aria-autocomplete="list" aria-multiline="false" class="ant-select-search__field" style="width: 0px;"><span class="ant-select-search__field__mirror">&nbsp;</span></span></li>
 </div><span class="ant-select-search__field__placeholder" style="display: block;">Please select</span></span></span>
 `;
 
-exports[`renders ./components/tree-select/demo/treeData.md correctly 1`] = `
-<span class="ant-select ant-select-enabled" style="width: 300px;"><span role="combobox" aria-autocomplete="list" aria-haspopup="true" tabindex="0" class="ant-select-selection
-            ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>
-`;
+exports[`renders ./components/tree-select/demo/suffix.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled ant-select-allow-clear" style="width: 300px;"><span class="ant-select-selection ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><i class="anticon anticon-smile"><svg viewBox="64 64 896 896" data-icon="smile" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M288 421a48 48 0 1 0 96 0 48 48 0 1 0-96 0zm352 0a48 48 0 1 0 96 0 48 48 0 1 0-96 0zM512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm263 711c-34.2 34.2-74 61-118.3 79.8C611 874.2 562.3 884 512 884c-50.3 0-99-9.8-144.8-29.2A370.4 370.4 0 0 1 248.9 775c-34.2-34.2-61-74-79.8-118.3C149.8 611 140 562.3 140 512s9.8-99 29.2-144.8A370.4 370.4 0 0 1 249 248.9c34.2-34.2 74-61 118.3-79.8C413 149.8 461.7 140 512 140c50.3 0 99 9.8 144.8 29.2A370.4 370.4 0 0 1 775.1 249c34.2 34.2 61 74 79.8 118.3C874.2 413 884 461.7 884 512s-9.8 99-29.2 144.8A368.89 368.89 0 0 1 775 775zM664 533h-48.1c-4.2 0-7.8 3.2-8.1 7.4C604 589.9 562.5 629 512 629s-92.1-39.1-95.8-88.6c-.3-4.2-3.9-7.4-8.1-7.4H360a8 8 0 0 0-8 8.4c4.4 84.3 74.5 151.6 160 151.6s155.6-67.3 160-151.6a8 8 0 0 0-8-8.4z"></path></svg></i></span></span></span>`;
+
+exports[`renders ./components/tree-select/demo/treeData.md correctly 1`] = `<span role="combobox" aria-haspopup="listbox" tabindex="0" class="ant-select ant-select-enabled" style="width: 300px;"><span class="ant-select-selection ant-select-selection--single"><span class="ant-select-selection__rendered"><span class="ant-select-selection__placeholder">Please select</span></span><span class="ant-select-arrow" style="outline: none;"><i class="ant-select-arrow-icon anticon anticon-down"><svg viewBox="64 64 896 896" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" class=""><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></i></span></span></span>`;
diff --git a/components/tree-select/demo/basic.md b/components/tree-select/demo/basic.md
index 735280343..39cdbfece 100644
--- a/components/tree-select/demo/basic.md
+++ b/components/tree-select/demo/basic.md
@@ -22,7 +22,7 @@ The most basic usage.
   >
     <a-tree-select-node value='parent 1' title='parent 1' key='0-1'>
       <a-tree-select-node value='parent 1-0' title='parent 1-0' key='0-1-1'>
-        <a-tree-select-node value='leaf1' title='my leaf' key='random' />
+        <a-tree-select-node :selectable="false" value='leaf1' title='my leaf' key='random' />
         <a-tree-select-node value='leaf2' title='your leaf' key='random1' />
       </a-tree-select-node>
       <a-tree-select-node value='parent 1-1' title='parent 1-1' key='random2'>
@@ -39,12 +39,13 @@ The most basic usage.
 export default {
   data () {
     return {
+      treeExpandedKeys: [],
       value: undefined,
     }
   },
   methods: {
     onChange (value) {
-      console.log(arguments)
+      console.log(value)
       this.value = value
     },
   },
diff --git a/components/tree-select/demo/checkable.md b/components/tree-select/demo/checkable.md
index 9abca3780..49be73220 100644
--- a/components/tree-select/demo/checkable.md
+++ b/components/tree-select/demo/checkable.md
@@ -18,7 +18,6 @@ Multiple and checkable.
     treeCheckable
     :showCheckedStrategy="SHOW_PARENT"
     searchPlaceholder='Please select'
-    treeNodeFilterProp='label'
   />
 </template>
 
@@ -27,29 +26,29 @@ import { TreeSelect } from 'ant-design-vue'
 const SHOW_PARENT = TreeSelect.SHOW_PARENT
 
 const treeData = [{
-  label: 'Node1',
+  title: 'Node1',
   value: '0-0',
   key: '0-0',
   children: [{
-    label: 'Child Node1',
+    title: 'Child Node1',
     value: '0-0-0',
     key: '0-0-0',
   }],
 }, {
-  label: 'Node2',
+  title: 'Node2',
   value: '0-1',
   key: '0-1',
   children: [{
-    label: 'Child Node3',
+    title: 'Child Node3',
     value: '0-1-0',
     key: '0-1-0',
     disabled: true,
   }, {
-    label: 'Child Node4',
+    title: 'Child Node4',
     value: '0-1-1',
     key: '0-1-1',
   }, {
-    label: 'Child Node5',
+    title: 'Child Node5',
     value: '0-1-2',
     key: '0-1-2',
   }],
@@ -64,7 +63,7 @@ export default {
   },
   methods: {
     onChange (value) {
-      console.log('onChange ', value, arguments)
+      console.log('onChange ', value)
       this.value = value
     },
   },
diff --git a/components/tree-select/demo/index.vue b/components/tree-select/demo/index.vue
index a0e0f736d..7a74cb360 100644
--- a/components/tree-select/demo/index.vue
+++ b/components/tree-select/demo/index.vue
@@ -3,6 +3,7 @@ import Basic from './basic'
 import Checkable from './checkable'
 import Multiple from './multiple'
 import TreeData from './treeData'
+import Suffix from './suffix'
 
 import CN from '../index.zh-CN.md'
 import US from '../index.en-US.md'
@@ -35,6 +36,7 @@ export default {
         <Checkable/>
         <Multiple/>
         <TreeData/>
+        <Suffix />
         <api>
           <template slot='cn'>
             <CN/>
diff --git a/components/tree-select/demo/multiple.md b/components/tree-select/demo/multiple.md
index 3fcf57a50..789d9bf89 100644
--- a/components/tree-select/demo/multiple.md
+++ b/components/tree-select/demo/multiple.md
@@ -47,14 +47,14 @@ export default {
   },
   methods: {
     onChange (value) {
-      console.log(arguments)
+      console.log(value)
       this.value = value
     },
     onSearch () {
-      console.log(arguments)
+      console.log(...arguments)
     },
     onSelect () {
-      console.log(arguments)
+      console.log(...arguments)
     },
   },
 }
diff --git a/components/tree-select/demo/suffix.md b/components/tree-select/demo/suffix.md
new file mode 100644
index 000000000..1ea335936
--- /dev/null
+++ b/components/tree-select/demo/suffix.md
@@ -0,0 +1,54 @@
+<cn>
+#### 后缀图标
+最简单的用法。
+</cn>
+
+<us>
+#### Suffix
+The most basic usage.
+</us>
+
+```html
+<template>
+  <a-tree-select
+    showSearch
+    style="width: 300px"
+    :value="value"
+    :dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
+    placeholder='Please select'
+    allowClear
+    treeDefaultExpandAll
+    @change="onChange"
+  >
+    <a-icon slot="suffixIcon" type="smile" />
+    <a-tree-select-node value='parent 1' title='parent 1' key='0-1'>
+      <a-tree-select-node value='parent 1-0' title='parent 1-0' key='0-1-1'>
+        <a-tree-select-node value='leaf1' title='my leaf' key='random' />
+        <a-tree-select-node value='leaf2' title='your leaf' key='random1' />
+      </a-tree-select-node>
+      <a-tree-select-node value='parent 1-1' title='parent 1-1' key='random2'>
+        <a-tree-select-node value='sss' key='random3'>
+          <b style="color: #08c" slot="title">sss</b>
+        </a-tree-select-node>
+      </a-tree-select-node>
+    </a-tree-select-node>
+  </a-tree-select>
+</template>
+
+<script>
+
+export default {
+  data () {
+    return {
+      value: undefined,
+    }
+  },
+  methods: {
+    onChange (value) {
+      console.log(value)
+      this.value = value
+    },
+  },
+}
+</script>
+```
diff --git a/components/tree-select/demo/treeData.md b/components/tree-select/demo/treeData.md
index e207ce136..ad386ce80 100644
--- a/components/tree-select/demo/treeData.md
+++ b/components/tree-select/demo/treeData.md
@@ -16,10 +16,9 @@ The tree structure can be populated using `treeData` property. This is a quick a
     :treeData="treeData"
     placeholder='Please select'
     treeDefaultExpandAll
-    labelInValue
     v-model="value"
   >
-    <span style="color: #08c" slot="label" slot-scope="{key, value}" v-if="key='0-0-1'">
+    <span style="color: #08c" slot="title" slot-scope="{key, value}" v-if="key='0-0-1'">
       <a-icon type="home"/>Child Node1 {{value}}
     </span>
   </a-tree-select>
@@ -27,22 +26,22 @@ The tree structure can be populated using `treeData` property. This is a quick a
 
 <script>
 const treeData = [{
-  label: 'Node1',
+  title: 'Node1',
   value: '0-0',
   key: '0-0',
   children: [{
     value: '0-0-1',
     key: '0-0-1',
-    scopedSlots: { // custom label
-      label: 'label',
+    scopedSlots: { // custom title
+      title: 'title',
     },
   }, {
-    label: 'Child Node2',
+    title: 'Child Node2',
     value: '0-0-2',
     key: '0-0-2',
   }],
 }, {
-  label: 'Node2',
+  title: 'Node2',
   value: '0-1',
   key: '0-1',
 }]
diff --git a/components/tree-select/index.en-US.md b/components/tree-select/index.en-US.md
index eac021c0d..7332d25ad 100644
--- a/components/tree-select/index.en-US.md
+++ b/components/tree-select/index.en-US.md
@@ -21,16 +21,17 @@
 | showCheckedStrategy | The way show selected item in box. **Default:** just show child nodes. **`TreeSelect.SHOW_ALL`:** show all checked treeNodes (include parent treeNode). **`TreeSelect.SHOW_PARENT`:** show checked treeNodes (just show parent treeNode). | enum { TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
 | showSearch | Whether to display a search input in the dropdown menu(valid only in the single mode) | boolean | false |
 | size | To set the size of the select input, options: `large` `small` | string | 'default' |
+| suffixIcon | The custom suffix icon | VNode \| slot | - |
 | treeCheckable | Whether to show checkbox on the treeNodes | boolean | false |
 | treeCheckStrictly | Whether to check nodes precisely (in the `checkable` mode), means parent and child nodes are not associated, and it will make `labelInValue` be true | boolean | false |
 | treeData | Data of the treeNodes, manual construction work is no longer needed if this property has been set(ensure the Uniqueness of each value) | array&lt;{ value, label, children, [disabled, disableCheckbox, selectable] }> | \[] |
 | treeDataSimpleMode | Enable simple mode of treeData.(treeData should like this: [{id:1, pId:0, value:'1', label:"test1",...},...], pId is parent node's id) | false\|Array&lt;{ id: string, pId: string, rootPId: null }> | false |
 | treeDefaultExpandAll | Whether to expand all treeNodes by default | boolean | false |
 | treeDefaultExpandedKeys | Default expanded treeNodes | string\[] | - |
+| treeExpandedKeys | Set expanded keys | string\[] | - |
 | treeNodeFilterProp | Will be used for filtering if `filterTreeNode` returns true | string | 'value' |
 | treeNodeLabelProp | Will render as content of select | string | 'title' |
 | value(v-model) | To set the current selected treeNode(s). | string\|string\[] | - |
-| suffixIcon | The custom suffix icon | VNode \| slot | - |
 
 ### Events
 | Events Name | Description | Arguments |
@@ -38,6 +39,7 @@
 | change | A callback function, can be executed when selected treeNodes or input value change | function(value, label, extra) |
 | search | A callback function, can be executed when the search input changes. | function(value: string) |
 | select | A callback function, can be executed when you select a treeNode. | function(value, node, extra) |
+| treeExpand | A callback function, can be executed when treeNode expanded | function(expandedKeys) |
 
 ### Tree Methods
 
@@ -52,6 +54,7 @@
 
 | Property | Description | Type | Default |
 | -------- | ----------- | ---- | ------- |
+| selectable | can be selected | boolean | true |
 | disableCheckbox | Disables the checkbox of the treeNode | boolean | false |
 | disabled | Disabled or not | boolean | false |
 | isLeaf | Leaf node or not | boolean | false |
diff --git a/components/tree-select/index.jsx b/components/tree-select/index.jsx
index e5e345239..ac44386a7 100644
--- a/components/tree-select/index.jsx
+++ b/components/tree-select/index.jsx
@@ -136,10 +136,12 @@ const TreeSelect = {
           dropdownStyle: { maxHeight: '100vh', overflow: 'auto', ...dropdownStyle },
           treeCheckable: checkable,
           notFoundContent: notFoundContent || locale.notFoundContent,
+          __propsSymbol__: Symbol(),
         },
         class: cls,
         on: { ...this.$listeners, change: this.onChange },
         ref: 'vcTreeSelect',
+        scopedSlots: this.$scopedSlots,
       }
       return (
         <VcTreeSelect {...VcTreeSelectProps}>{filterEmpty(this.$slots.default)}</VcTreeSelect>
diff --git a/components/tree-select/index.zh-CN.md b/components/tree-select/index.zh-CN.md
index ebb95f66e..bb252c21c 100644
--- a/components/tree-select/index.zh-CN.md
+++ b/components/tree-select/index.zh-CN.md
@@ -21,24 +21,26 @@
 | showCheckedStrategy | 定义选中项回填的方式。`TreeSelect.SHOW_ALL`: 显示所有选中节点(包括父节点). `TreeSelect.SHOW_PARENT`: 只显示父节点(当父节点下所有子节点都选中时). 默认只显示子节点. | enum{TreeSelect.SHOW_ALL, TreeSelect.SHOW_PARENT, TreeSelect.SHOW_CHILD } | TreeSelect.SHOW_CHILD |
 | showSearch | 在下拉中显示搜索框(仅在单选模式下生效) | boolean | false |
 | size | 选择框大小,可选 `large` `small` | string | 'default' |
+| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - |
 | treeCheckable | 显示 checkbox | boolean | false |
 | treeCheckStrictly | checkable 状态下节点选择完全受控(父子节点选中状态不再关联),会使得 `labelInValue` 强制为 true | boolean | false |
 | treeData | treeNodes 数据,如果设置则不需要手动构造 TreeNode 节点(value 在整个树范围内唯一) | array&lt;{value, label, children, [disabled, disableCheckbox, selectable]}> | \[] |
 | treeDataSimpleMode | 使用简单格式的 treeData,具体设置参考可设置的类型 (此时 treeData 应变为这样的数据结构: [{id:1, pId:0, value:'1', label:"test1",...},...], `pId` 是父节点的 id) | false\|Array&lt;{ id: string, pId: string, rootPId: null }> | false |
 | treeDefaultExpandAll | 默认展开所有树节点 | boolean | false |
 | treeDefaultExpandedKeys | 默认展开的树节点 | string\[] | - |
+| treeExpandedKeys | 设置展开的树节点 | string\[] | - |
 | treeNodeFilterProp | 输入项过滤对应的 treeNode 属性 | string | 'value' |
 | treeNodeLabelProp | 作为显示的 prop 设置 | string | 'title' |
 | value(v-model) | 指定当前选中的条目 | string/string\[] | - |
-| suffixIcon | 自定义的选择框后缀图标 | VNode \| slot | - |
 
 ### 事件
 
 | 事件名称 | 说明 | 回调参数 |
 | --- | --- | --- |
-| change | 选中树节点时调用此函数 | function(value, label, extra) | - |
-| search | 文本框值变化时回调 | function(value: string) | - |
-| select | 被选中时调用 | function(value, node, extra) | - |
+| change | 选中树节点时调用此函数 | function(value, label, extra) |
+| search | 文本框值变化时回调 | function(value: string) |
+| select | 被选中时调用 | function(value, node, extra) |
+| treeExpand | 展开节点时调用 | function(expandedKeys) |
 
 ### Tree 方法
 
@@ -53,6 +55,7 @@
 
 | 参数 | 说明 | 类型 | 默认值 |
 | --- | --- | --- | --- |
+| selectable | 是否可选 | boolean | true |
 | disableCheckbox | 禁掉 checkbox | boolean | false |
 | disabled | 是否禁用 | boolean | false |
 | isLeaf | 是否是叶子节点 | boolean | false |
diff --git a/components/tree-select/interface.jsx b/components/tree-select/interface.jsx
index 22069cf87..93bfc6870 100644
--- a/components/tree-select/interface.jsx
+++ b/components/tree-select/interface.jsx
@@ -11,6 +11,14 @@ export const TreeData = PropTypes.shape({
 
 export const TreeSelectProps = () => ({
   ...AbstractSelectProps(),
+  autoFocus: PropTypes.bool,
+  dropdownStyle: PropTypes.object,
+  filterTreeNode: PropTypes.oneOfType([Function, Boolean]),
+  getPopupContainer: PropTypes.func,
+  labelInValue: PropTypes.bool,
+  loadData: PropTypes.func,
+  maxTagCount: PropTypes.number,
+  maxTagPlaceholder: PropTypes.any,
   value: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array]),
   defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
   multiple: PropTypes.bool,
@@ -18,21 +26,18 @@ export const TreeSelectProps = () => ({
   // onChange: (value: any, label: any) => void,
   // onSearch: (value: any) => void,
   searchPlaceholder: PropTypes.string,
-  dropdownClassName: PropTypes.string,
-  dropdownStyle: PropTypes.object,
-  dropdownMatchSelectWidth: PropTypes.bool,
-  treeDefaultExpandAll: PropTypes.bool,
+  showCheckedStrategy: PropTypes.oneOf(['SHOW_ALL', 'SHOW_PARENT', 'SHOW_CHILD']),
+  suffixIcon: PropTypes.any,
   treeCheckable: PropTypes.bool,
-  treeDefaultExpandedKeys: PropTypes.arrayOf(String),
-  filterTreeNode: PropTypes.func,
-  treeNodeFilterProp: PropTypes.string,
-  treeNodeLabelProp: PropTypes.string,
+  treeCheckStrictly: PropTypes.bool,
   treeData: PropTypes.arrayOf(Object),
   treeDataSimpleMode: PropTypes.oneOfType([Boolean, Object]),
-  loadData: PropTypes.func,
-  showCheckedStrategy: PropTypes.oneOf(['SHOW_ALL', 'SHOW_PARENT', 'SHOW_CHILD']),
-  labelInValue: PropTypes.bool,
-  treeCheckStrictly: PropTypes.bool,
-  getPopupContainer: PropTypes.func,
-  suffixIcon: PropTypes.any,
+
+  dropdownClassName: PropTypes.string,
+  dropdownMatchSelectWidth: PropTypes.bool,
+  treeDefaultExpandAll: PropTypes.bool,
+  treeExpandedKeys: PropTypes.arrayOf(String),
+  treeDefaultExpandedKeys: PropTypes.arrayOf(String),
+  treeNodeFilterProp: PropTypes.string,
+  treeNodeLabelProp: PropTypes.string,
 })
diff --git a/components/tree/Tree.jsx b/components/tree/Tree.jsx
index 41ad942d4..28bc9bde4 100644
--- a/components/tree/Tree.jsx
+++ b/components/tree/Tree.jsx
@@ -2,7 +2,7 @@ import warning from 'warning'
 import { Tree as VcTree, TreeNode } from '../vc-tree'
 import animation from '../_util/openAnimation'
 import PropTypes from '../_util/vue-types'
-import { initDefaultProps, getOptionProps } from '../_util/props-util'
+import { initDefaultProps, getOptionProps, filterEmpty } from '../_util/props-util'
 import Icon from '../icon'
 
 function TreeProps () {
@@ -179,7 +179,7 @@ export default {
       props: {
         ...props,
         checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
-        children: this.$slots.default || [],
+        children: filterEmpty(this.$slots.default || []),
         __propsSymbol__: Symbol(),
         switcherIcon: this.renderSwitcherIcon,
       },
diff --git a/components/vc-select/Select.jsx b/components/vc-select/Select.jsx
index 721220ae7..33b1aeabb 100644
--- a/components/vc-select/Select.jsx
+++ b/components/vc-select/Select.jsx
@@ -107,7 +107,7 @@ const Select = {
       this.__propsSymbol__,
       'Replace slots.default with props.children and pass props.__propsSymbol__'
     )
-    return {
+    const state = {
       _value: this.getValueFromProps(props, true), // true: use default value
       _inputValue: props.combobox ? this.getInputValueForCombobox(
         props,
@@ -119,10 +119,10 @@ const Select = {
       // a flag for aviod redundant getOptionsInfoFromProps call
       _skipBuildOptionsInfo: true,
     }
-  },
-  beforeMount () {
-    const state = this.getDerivedStateFromProps(getOptionProps(this), this.$data)
-    Object.assign(this.$data, state)
+    return {
+      ...state,
+      ...this.getDerivedStateFromProps(props, state),
+    }
   },
 
   mounted () {
diff --git a/components/vc-tree-select/assets/select.less b/components/vc-tree-select/assets/select.less
index 7efc6c2e4..908ddb88b 100644
--- a/components/vc-tree-select/assets/select.less
+++ b/components/vc-tree-select/assets/select.less
@@ -37,7 +37,8 @@
     top: 1px;
     right: 1px;
     width: 20px;
-    b {
+    &:after {
+      content: '';
       border-color: #999999 transparent transparent transparent;
       border-style: solid;
       border-width: 5px 4px 0 4px;
@@ -66,22 +67,30 @@
     &__clear {
       font-weight: bold;
       position: absolute;
+    }
+  }
 
-      &:after {
-        content: '×'
+  &-enabled {
+    .@{selectPrefixCls}-selection {
+      &:hover {
+        border-color: #23c0fa;
+        box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
+      }
+      &:active {
+        border-color: #2db7f5;
+      }
+    }
+
+    &.@{selectPrefixCls}-focused {
+      .@{selectPrefixCls}-selection {
+        //border-color: #23c0fa;
+        border-color: #7700fa;
+        box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
       }
     }
   }
 
-  &-enabled &-selection {
-    &:hover {
-      border-color: #23c0fa;
-      box-shadow: 0 0 2px fadeout(#2db7f5, 20%);
-    }
-    &:active {
-      border-color: #2db7f5;
-    }
-  }
+
 
   &-selection--single {
     height: 28px;
@@ -105,6 +114,9 @@
     .@{selectPrefixCls}-selection__clear {
       top: 5px;
       right: 20px;
+      &:after {
+        content: '×';
+      }
     }
   }
 
@@ -117,7 +129,7 @@
       cursor: not-allowed;
       color: #ccc;
 
-      &:hover {
+      &:hover{
         cursor: not-allowed;
         color: #ccc;
       }
@@ -130,6 +142,7 @@
   }
 
   &-search__field__placeholder {
+    display: block;
     position: absolute;
     top: 0;
     left: 3px;
@@ -288,6 +301,7 @@
       top: 0;
       right: 2px;
       transition: opacity .3s, transform .3s;
+
       &:before {
         content: '×'
       }
@@ -487,7 +501,7 @@
   }
 
   &-open {
-    .@{selectPrefixCls}-arrow b {
+    .@{selectPrefixCls}-arrow:after {
       border-color: transparent transparent #888 transparent;
       border-width: 0 4px 5px 4px;
     }
@@ -498,3 +512,25 @@
     padding: 8px;
   }
 }
+
+.custom-icon-demo {
+  .@{selectPrefixCls} {
+    &-selection__choice__remove {
+      &:before {
+        content: '';
+      }
+    }
+
+    &-arrow {
+      &:after {
+        display: none;
+      }
+    }
+
+    &-selection__clear {
+      &:after {
+        content: '';
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/components/vc-tree-select/assets/tree.less b/components/vc-tree-select/assets/tree.less
index 50744851a..7a74b2138 100644
--- a/components/vc-tree-select/assets/tree.less
+++ b/components/vc-tree-select/assets/tree.less
@@ -38,7 +38,7 @@
       }
     }
     &.filter-node {
-      > a {
+      > .@{treePrefixCls}-node-content-wrapper {
         color: #a60000!important;
         font-weight: bold!important;
       }
diff --git a/components/vc-tree-select/demo/basic.jsx b/components/vc-tree-select/demo/basic.jsx
index 2eaf66b6d..7881c1bf9 100644
--- a/components/vc-tree-select/demo/basic.jsx
+++ b/components/vc-tree-select/demo/basic.jsx
@@ -1,12 +1,11 @@
 /* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
-
+import BaseMixin from '../../_util/BaseMixin'
 import '../assets/index.less'
-import './demo.less'
-
 import '../../vc-dialog/assets/index.less'
 import Dialog from '../../vc-dialog'
-import TreeSelect, { TreeNode, SHOW_PARENT } from '../index'
+import TreeSelect, { TreeNode, SHOW_PARENT } from '../src/index'
 import { gData } from './util'
+import './demo.less'
 
 function isLeaf (value) {
   if (!value) {
@@ -51,89 +50,90 @@ function findPath (value, data) {
 }
 
 export default {
-  data () {
-    return {
-      tsOpen: false,
-      visible: false,
-      inputValue: '0-0-0-label',
-      value: '0-0-0-value1',
-      // value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
-      lv: { value: '0-0-0-value', label: 'spe label' },
-      multipleValue: [],
-      simpleTreeData: [
-        { key: 1, pId: 0, label: 'test1', value: 'test1' },
-        { key: 121, pId: 0, label: 'test1', value: 'test121' },
-        { key: 11, pId: 1, label: 'test11', value: 'test11' },
-        { key: 12, pId: 1, label: 'test12', value: 'test12' },
-        { key: 111, pId: 11, label: 'test111', value: 'test111' },
-      ],
-      treeDataSimpleMode: {
-        id: 'key',
-        rootPId: 0,
-      },
-    }
-  },
-
-  mounted () {
-    // console.log(this.refs.mul.getInputDOMNode());
-    // this.refs.mul.getInputDOMNode().setAttribute('disabled', true);
-  },
+  mixins: [BaseMixin],
+  data: () => ({
+    tsOpen: false,
+    visible: false,
+    searchValue: '0-0-0-label',
+    value: '0-0-0-value1',
+    // value: ['0-0-0-0-value', '0-0-0-1-value', '0-0-0-2-value'],
+    lv: { value: '0-0-0-value', label: 'spe label' },
+    multipleValue: [],
+    simpleSearchValue: 'test111',
+    simpleTreeData: [
+      { key: 1, pId: 0, label: 'test1', value: 'test1' },
+      { key: 121, pId: 0, label: 'test2', value: 'test2' },
+      { key: 11, pId: 1, label: 'test11', value: 'test11' },
+      { key: 12, pId: 1, label: 'test12', value: 'test12' },
+      { key: 111, pId: 11, label: 'test111', value: 'test111' },
+    ],
+    treeDataSimpleMode: {
+      id: 'key',
+      rootPId: 0,
+    },
+  }),
   methods: {
-    onClick  () {
-      this.visible = true
+    onClick () {
+      this.setState({
+        visible: true,
+      })
     },
 
-    onClose () {
-      this.visible = false
+    onClose  () {
+      this.setState({
+        visible: false,
+      })
     },
 
     onSearch (value) {
-      console.log(value, arguments)
+      console.log('Do Search:', value, arguments)
+      this.setState({ searchValue: value })
     },
 
-    onChange (value) {
-      console.log('onChange', arguments)
-      this.value = value
+    onChange (value, ...rest) {
+      console.log('onChange', value, ...rest)
+      this.setState({ value })
     },
 
-    onChangeChildren (value) {
-      console.log('onChangeChildren', arguments)
+    onChangeChildren (...args) {
+      console.log('onChangeChildren', ...args)
+      const value = args[0]
       const pre = value ? this.value : undefined
-      this.value = isLeaf(value) ? value : pre
+      this.setState({ value: isLeaf(value) ? value : pre })
     },
 
-    onChangeLV (value) {
+    onChangeLV  (value) {
       console.log('labelInValue', arguments)
       if (!value) {
-        this.lv = undefined
+        this.setState({ lv: undefined })
         return
       }
       const path = findPath(value.value, gData).map(i => i.label).reverse().join(' > ')
-      this.lv = { value: value.value, label: path }
+      this.setState({ lv: { value: value.value, label: path }})
     },
 
     onMultipleChange  (value) {
       console.log('onMultipleChange', arguments)
-      this.multipleValue = value
+      this.setState({ multipleValue: value })
     },
 
     onSelect () {
       // use onChange instead
-      console.log(...arguments)
+      console.log(arguments)
     },
 
-    onDropdownVisibleChange  (visible, info) {
+    onDropdownVisibleChange (visible, info) {
       console.log(visible, this.value, info)
       if (Array.isArray(this.value) && this.value.length > 1 &&
         this.value.length < 3) {
-        alert('please select more than two item or less than one item.')
+        window.alert('please select more than two item or less than one item.')
         return false
       }
       return true
     },
 
-    filterTreeNode (input, child) {
-      return String(child.title).indexOf(input) === 0
+    filterTreeNode  (input, child) {
+      return String(child.data.props.title).indexOf(input) === 0
     },
   },
 
@@ -167,10 +167,10 @@ export default {
               onSearch={this.onSearch}
               onChange={this.onChange}
               onSelect={this.onSelect}
+              __propsSymbol__={Symbol()}
             />
           </div>
         </Dialog> : null}
-
         <h2>single select</h2>
         <TreeSelect
           style={{ width: '300px' }}
@@ -180,31 +180,35 @@ export default {
           placeholder={<i>请下拉选择</i>}
           searchPlaceholder='please search'
           showSearch allowClear treeLine
-          inputValue={this.inputValue}
+          searchValue={this.searchValue}
           value={this.value}
           treeData={gData}
           treeNodeFilterProp='label'
           filterTreeNode={false}
           onSearch={this.onSearch}
           open={this.tsOpen}
-          onChange={(value) => {
-            console.log('onChange', value, arguments)
+          onChange={(value, ...args) => {
+            console.log('onChange', value, ...args)
             if (value === '0-0-0-0-value') {
-              this.tsOpen = true
+              this.setState({ tsOpen: true })
             } else {
-              this.tsOpen = false
+              this.setState({ tsOpen: false })
             }
-            this.value = value
+            this.setState({ value })
           } }
           dropdownVisibleChange={(v, info) => {
-            console.log('single dropdownVisibleChange', v, info)
+            console.log('single onDropdownVisibleChange', v, info)
             // document clicked
             if (info.documentClickClose && this.value === '0-0-0-0-value') {
               return false
             }
+            this.setState({
+              tsOpen: v,
+            })
             return true
           } }
           onSelect={this.onSelect}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>single select (just select children)</h2>
@@ -221,10 +225,11 @@ export default {
           treeNodeFilterProp='label'
           filterTreeNode={false}
           onChange={this.onChangeChildren}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>multiple select</h2>
-        <TreeSelect ref='mul'
+        <TreeSelect
           style={{ width: '300px' }}
           transitionName='rc-tree-select-dropdown-slide-up'
           choiceTransitionName='rc-tree-select-selection__choice-zoom'
@@ -238,6 +243,7 @@ export default {
           onChange={this.onMultipleChange}
           onSelect={this.onSelect}
           allowClear
+          __propsSymbol__={Symbol()}
         />
 
         <h2>check select</h2>
@@ -247,17 +253,23 @@ export default {
           choiceTransitionName='rc-tree-select-selection__choice-zoom'
           dropdownStyle={{ height: '200px', overflow: 'auto' }}
           dropdownPopupAlign={{ overflow: { adjustY: 0, adjustX: 0 }, offset: [0, 2] }}
-          onDropdownVisibleChange={this.onDropdownVisibleChange}
+          dropdownVisibleChange={this.onDropdownVisibleChange}
           placeholder={<i>请下拉选择</i>}
           searchPlaceholder='please search'
           treeLine maxTagTextLength={10}
           value={this.value}
-          inputValue={null}
+          autoClearSearchValue
           treeData={gData}
           treeNodeFilterProp='title'
           treeCheckable showCheckedStrategy={SHOW_PARENT}
           onChange={this.onChange}
           onSelect={this.onSelect}
+          maxTagCount={2}
+          maxTagPlaceholder={(valueList) => {
+            console.log('Max Tag Rest Value:', valueList)
+            return `${valueList.length} rest...`
+          }}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>labelInValue & show path</h2>
@@ -274,6 +286,7 @@ export default {
           treeNodeFilterProp='label'
           filterTreeNode={false}
           onChange={this.onChangeLV}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>use treeDataSimpleMode</h2>
@@ -283,7 +296,10 @@ export default {
           placeholder={<i>请下拉选择</i>}
           searchPlaceholder='please search'
           treeLine maxTagTextLength={10}
-          inputValue={'test111'}
+          searchValue={this.simpleSearchValue}
+          onSearch={(simpleSearchValue) => {
+            this.setState({ simpleSearchValue })
+          }}
           value={this.value}
           treeData={this.simpleTreeData}
           treeNodeFilterProp='title'
@@ -291,13 +307,14 @@ export default {
           treeCheckable showCheckedStrategy={SHOW_PARENT}
           onChange={this.onChange}
           onSelect={this.onSelect}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>Testing in extreme conditions (Boundary conditions test) </h2>
         <TreeSelect
           style={{ width: '200px' }}
           dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
-          defaultValue={'leaf1'} multiple treeCheckable showCheckedStrategy={SHOW_PARENT}
+          defaultValue='leaf1' multiple treeCheckable showCheckedStrategy={SHOW_PARENT}
           treeDefaultExpandAll
           treeData={[
             { key: '', value: '', label: 'empty value', children: [] },
@@ -308,18 +325,20 @@ export default {
               ],
             },
           ]}
-          onChange={(val) => console.log(val, arguments)}
+          onChange={(val, ...args) => console.log(val, ...args)}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>use TreeNode Component (not recommend)</h2>
         <TreeSelect
           style={{ width: '200px' }}
           dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
-          defaultValue={'leaf1'}
+          defaultValue='leaf1'
           treeDefaultExpandAll
           treeNodeFilterProp='title'
           filterTreeNode={this.filterTreeNode}
-          onChange={(val) => console.log(val, arguments)}
+          onChange={(val, ...args) => console.log(val, ...args)}
+          __propsSymbol__={Symbol()}
         >
           <TreeNode value='' title='parent 1' key=''>
             <TreeNode value='parent 1-0' title='parent 1-0' key='0-1-0'>
@@ -331,7 +350,7 @@ export default {
                 title={<span style={{ color: 'red' }}>sss</span>} key='random3'
               />
               <TreeNode value='same value1' title='same txtle' key='0-1-1-1'>
-                <TreeNode value='same value10' title='same titlexd' key='0-1-1-1-0' />
+                <TreeNode value='same value10' title='same titlexd' key='0-1-1-1-0' style={{ color: 'red', background: 'green' }} />
               </TreeNode>
             </TreeNode>
           </TreeNode>
diff --git a/components/vc-tree-select/demo/big-data-generator.jsx b/components/vc-tree-select/demo/big-data-generator.js
similarity index 100%
rename from components/vc-tree-select/demo/big-data-generator.jsx
rename to components/vc-tree-select/demo/big-data-generator.js
diff --git a/components/vc-tree-select/demo/big-data.jsx b/components/vc-tree-select/demo/big-data.js
similarity index 96%
rename from components/vc-tree-select/demo/big-data.jsx
rename to components/vc-tree-select/demo/big-data.js
index 9ca93ec24..d9b9c9215 100644
--- a/components/vc-tree-select/demo/big-data.jsx
+++ b/components/vc-tree-select/demo/big-data.js
@@ -56,6 +56,7 @@ export default {
             treeCheckable
             showCheckedStrategy={SHOW_PARENT}
             onChange={this.onChange}
+            __propsSymbol__={Symbol()}
           />
         </div>
         <div>
@@ -70,6 +71,7 @@ export default {
             treeCheckStrictly
             showCheckedStrategy={SHOW_PARENT}
             onChange={this.onChangeStrictly}
+            __propsSymbol__={Symbol()}
           />
         </div>
       </div>
diff --git a/components/vc-tree-select/demo/controlled.jsx b/components/vc-tree-select/demo/controlled.jsx
new file mode 100644
index 000000000..5a56572b5
--- /dev/null
+++ b/components/vc-tree-select/demo/controlled.jsx
@@ -0,0 +1,68 @@
+/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
+
+import BaseMixin from '../../_util/BaseMixin'
+import '../assets/index.less'
+import '../../vc-dialog/assets/index.less'
+import TreeSelect, { TreeNode } from '../src/index'
+import './demo.less'
+
+export default {
+  mixins: [BaseMixin],
+  data: () => ({
+    treeExpandedKeys: [],
+  }),
+  methods: {
+    onTreeExpand (treeExpandedKeys) {
+      this.setState({
+        treeExpandedKeys,
+      })
+    },
+
+    setTreeExpandedKeys () {
+      this.setState({
+        treeExpandedKeys: ['000', '0-1-0'],
+      })
+    },
+
+  },
+
+  render () {
+    const { treeExpandedKeys } = this
+
+    return (
+      <div>
+        <h2>Conrolled treeExpandedKeys</h2>
+        <TreeSelect
+          style={{ width: '200px' }}
+          dropdownStyle={{ maxHeight: '200px', overflow: 'auto' }}
+          treeExpandedKeys={treeExpandedKeys}
+          onTreeExpand={this.onTreeExpand}
+          __propsSymbol__={Symbol()}
+        >
+          <TreeNode value='' title='parent 1' key='000'>
+            <TreeNode value='parent 1-0' title='parent 1-0' key='0-1-0'>
+              <TreeNode value='leaf1' title='my leaf' key='random' />
+              <TreeNode value='leaf2' title='your leaf' key='random1' disabled />
+            </TreeNode>
+            <TreeNode value='parent 1-1' title='parent 1-1' key='0-1-1'>
+              <TreeNode value='sss'
+                title={<span style={{ color: 'red' }}>sss</span>} key='random3'
+              />
+              <TreeNode value='same value1' title='same txtle' key='0-1-1-1'>
+                <TreeNode value='same value10' title='same titlexd' key='0-1-1-1-0' style={{ color: 'red', background: 'green' }} />
+              </TreeNode>
+            </TreeNode>
+          </TreeNode>
+          <TreeNode value='same value2' title='same title' key='0-2'>
+            <TreeNode value='2same value' title='2same title' key='0-2-0' />
+          </TreeNode>
+          <TreeNode value='same value3' title='same title' key='0-3' />
+        </TreeSelect>
+        <button onClick={this.setTreeExpandedKeys}>
+          Set treeExpandedKeys
+        </button>
+      </div>
+    )
+  },
+}
+
diff --git a/components/vc-tree-select/demo/custom-icons.jsx b/components/vc-tree-select/demo/custom-icons.jsx
new file mode 100644
index 000000000..301fe44e3
--- /dev/null
+++ b/components/vc-tree-select/demo/custom-icons.jsx
@@ -0,0 +1,113 @@
+/* eslint react/no-multi-comp:0, no-console:0, no-alert: 0 */
+
+import '../assets/index.less'
+import '../../vc-dialog/assets/index.less'
+import TreeSelect from '../src/index'
+import { gData } from './util'
+import './demo.less'
+
+const bubblePath = 'M632 888H392c-4.4 0-8 3.6-8 8v32c0 ' +
+  '17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-3' +
+  '2c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-3' +
+  '28 328 0 121.4 66 227.4 164 284.1V792c0 17.7 1' +
+  '4.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98' +
+  '-56.7 164-162.7 164-284.1 0-181.1-146.9-328-32' +
+  '8-328z m127.9 549.8L604 634.6V752H420V634.6l-3' +
+  '5.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4' +
+  ' 114.6-256 256-256s256 114.6 256 256c0 92.5-49' +
+  '.4 176.3-128.1 221.8z'
+
+const clearPath = 'M793 242H366v-74c0-6.7-7.7-10.4-12.9' +
+  '-6.3l-142 112c-4.1 3.2-4.1 9.4 0 12.6l142 112c' +
+  '5.2 4.1 12.9 0.4 12.9-6.3v-74h415v470H175c-4.4' +
+  ' 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h618c35.3 0 64-' +
+  '28.7 64-64V306c0-35.3-28.7-64-64-64z'
+
+const arrowPath = 'M765.7 486.8L314.9 134.7c-5.3-4.1' +
+  '-12.9-0.4-12.9 6.3v77.3c0 4.9 2.3 9.6 6.1 12.6l36' +
+  '0 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6' +
+  '.7 7.7 10.4 12.9 6.3l450.8-352.1c16.4-12.8 16.4-3' +
+  '7.6 0-50.4z'
+
+const getSvg = (h, path, iStyle = {}, style = {}) => {
+  return (
+    <i style={iStyle}>
+      <svg
+        viewBox='0 0 1024 1024'
+        width='1em'
+        height='1em'
+        fill='currentColor'
+        style={{ verticalAlign: '-.125em', ...style }}
+      >
+        <path d={path} />
+      </svg>
+    </i>
+  )
+}
+
+export default {
+  data () {
+    const h = this.$createElement
+    const switcherIcon = (obj) => {
+      if (obj.isLeaf) {
+        return getSvg(h, arrowPath,
+          { cursor: 'pointer', backgroundColor: 'white' },
+          { transform: 'rotate(270deg)' })
+      }
+      return getSvg(h, arrowPath,
+        { cursor: 'pointer', backgroundColor: 'white' },
+        { transform: `rotate(${obj.expanded ? 90 : 0}deg)` })
+    }
+
+    const inputIcon = getSvg(h, bubblePath)
+    const clearIcon = getSvg(h, clearPath)
+    const removeIcon = getSvg(h, clearPath)
+    return {
+      iconProps: {
+        inputIcon,
+        clearIcon,
+        removeIcon,
+        switcherIcon,
+      },
+
+      iconPropsFunction: {
+        inputIcon: () => inputIcon,
+        clearIcon: () => clearIcon,
+        removeIcon: () => removeIcon,
+        switcherIcon,
+      },
+    }
+  },
+  render () {
+    return (
+      <div class='custom-icon-demo'>
+        <h2>Single</h2>
+        <TreeSelect
+          treeData={gData}
+          placeholder={<span>Please Select</span>}
+          transitionName='rc-tree-select-dropdown-slide-up'
+          style={{ width: '300px' }}
+          dropdownStyle={{ maxHeight: '200px', overflow: 'auto', zIndex: 1500 }}
+          showSearch allowClear
+          {...{ props: { ...this.iconProps }}}
+          __propsSymbol__={Symbol()}
+        />
+        <br />
+        <h2>Multiple</h2>
+        <TreeSelect
+          treeData={gData}
+          multiple
+          placeholder={<span>Please Select</span>}
+          transitionName='rc-tree-select-dropdown-slide-up'
+          style={{ width: '300px' }}
+          dropdownStyle={{ maxHeight: '200px', overflow: 'auto', zIndex: 1500 }}
+          showSearch allowClear
+          {...{ props: { ...this.iconPropsFunction }}}
+          __propsSymbol__={Symbol()}
+        />
+      </div>
+    )
+  },
+
+}
+
diff --git a/components/vc-tree-select/demo/demo.less b/components/vc-tree-select/demo/demo.less
index e762c52e6..5d46fd9f8 100644
--- a/components/vc-tree-select/demo/demo.less
+++ b/components/vc-tree-select/demo/demo.less
@@ -1,4 +1,3 @@
-
 .rc-tree-select-selection--multiple {
   max-height: 50px;
   overflow-y: scroll;
@@ -12,4 +11,4 @@
   .rc-tree-select-selection--multiple {
     min-height: 50px;
   }
-}
+}
\ No newline at end of file
diff --git a/components/vc-tree-select/demo/disable.jsx b/components/vc-tree-select/demo/disable.js
similarity index 80%
rename from components/vc-tree-select/demo/disable.jsx
rename to components/vc-tree-select/demo/disable.js
index 2600215d8..b4604ed8d 100644
--- a/components/vc-tree-select/demo/disable.jsx
+++ b/components/vc-tree-select/demo/disable.js
@@ -1,6 +1,7 @@
 /* eslint react/no-multi-comp:0, no-console:0 */
+import BaseMixin from '../../_util/BaseMixin'
 import '../assets/index.less'
-import TreeSelect from '../index'
+import TreeSelect from '../src/index'
 
 const SHOW_PARENT = TreeSelect.SHOW_PARENT
 
@@ -33,20 +34,18 @@ const treeData = [{
 }]
 
 export default {
-  data () {
-    return {
-      value: ['0-0-0'],
-      disabled: false,
-    }
-  },
-
+  mixins: [BaseMixin],
+  data: () => ({
+    value: ['0-0-0'],
+    disabled: false,
+  }),
   methods: {
-    onChange  (value) {
+    onChange (value) {
       console.log('onChange ', value, arguments)
-      this.value = value
+      this.setState({ value })
     },
     switch (checked) {
-      this.disabled = checked
+      this.setState({ disabled: checked })
     },
   },
 
@@ -61,6 +60,7 @@ export default {
         treeCheckable: true,
         showCheckedStrategy: SHOW_PARENT,
         searchPlaceholder: 'Please select',
+        __propsSymbol__: Symbol(),
       },
       on: {
         change: this.onChange,
@@ -77,4 +77,3 @@ export default {
     )
   },
 }
-
diff --git a/components/vc-tree-select/demo/dynamic.jsx b/components/vc-tree-select/demo/dynamic.js
similarity index 59%
rename from components/vc-tree-select/demo/dynamic.jsx
rename to components/vc-tree-select/demo/dynamic.js
index 1e92fc062..7c5ff4e20 100644
--- a/components/vc-tree-select/demo/dynamic.jsx
+++ b/components/vc-tree-select/demo/dynamic.js
@@ -1,35 +1,36 @@
 /* eslint react/no-multi-comp:0, no-console:0 */
 
+import BaseMixin from '../../_util/BaseMixin'
 import '../assets/index.less'
-import TreeSelect from '../index'
+import TreeSelect from '../src/index'
 import { getNewTreeData, generateTreeNodes } from './util'
 
 export default {
-  data () {
-    return {
-      treeData: [
-        { label: 'pNode 01', value: '0-0', key: '0-0' },
-        { label: 'pNode 02', value: '0-1', key: '0-1' },
-        { label: 'pNode 03', value: '0-2', key: '0-2', isLeaf: true },
-      ],
-      // value: '0-0',
-      value: { value: '0-0-0-value', label: '0-0-0-label' },
-    }
-  },
-
+  mixins: [BaseMixin],
+  data: () => ({
+    treeData: [
+      { label: 'pNode 01', value: '0-0', key: '0-0' },
+      { label: 'pNode 02', value: '0-1', key: '0-1' },
+      { label: 'pNode 03', value: '0-2', key: '0-2', isLeaf: true },
+    ],
+    // value: '0-0',
+    value: { value: '0-0-0-value', label: '0-0-0-label' },
+  }),
   methods: {
-    onChange (value) {
+    onChange  (value) {
       console.log(value)
-      this.value = value
+      this.setState({
+        value,
+      })
     },
 
-    onLoadData  (treeNode) {
+    onLoadData (treeNode) {
       console.log(treeNode)
       return new Promise((resolve) => {
         setTimeout(() => {
           const treeData = [...this.treeData]
           getNewTreeData(treeData, treeNode.eventKey, generateTreeNodes(treeNode), 2)
-          this.treeData = treeData
+          this.setState({ treeData })
           resolve()
         }, 500)
       })
@@ -47,9 +48,9 @@ export default {
           value={this.value}
           onChange={this.onChange}
           loadData={this.onLoadData}
+          __propsSymbol__={Symbol()}
         />
       </div>
     )
   },
 }
-
diff --git a/components/vc-tree-select/demo/filter.jsx b/components/vc-tree-select/demo/filter.js
similarity index 97%
rename from components/vc-tree-select/demo/filter.jsx
rename to components/vc-tree-select/demo/filter.js
index 36f805386..c86a819ae 100644
--- a/components/vc-tree-select/demo/filter.jsx
+++ b/components/vc-tree-select/demo/filter.js
@@ -71,6 +71,7 @@ export default {
           treeCheckable
           onChange={this.onChange}
           onSelect={this.onSelect}
+          __propsSymbol__={Symbol()}
         />
 
         <h2>use treeDataSimpleMode</h2>
@@ -89,6 +90,7 @@ export default {
           treeCheckable showCheckedStrategy={SHOW_PARENT}
           onChange={this.onChange}
           onSelect={this.onSelect}
+          __propsSymbol__={Symbol()}
         />
         <button onClick={this.onDataChange}>change data</button>
       </div>
diff --git a/components/vc-tree-select/demo/form.js b/components/vc-tree-select/demo/form.js
index f66317371..dcae2f5f2 100644
--- a/components/vc-tree-select/demo/form.js
+++ b/components/vc-tree-select/demo/form.js
@@ -25,7 +25,7 @@ const TreeSelectInput = {
 
   render () {
     return (
-      <TreeSelect {...{ props: this.$props }} onChange={this.onChange.bind(this)} />
+      <TreeSelect {...{ props: this.$props }} onChange={this.onChange} />
     )
   },
 }
@@ -57,6 +57,7 @@ const Form = {
         multiple: true,
         treeData: gData,
         treeCheckable: true,
+        __propsSymbol__: Symbol(),
         // treeDefaultExpandAll: true,
       },
     }
diff --git a/components/vc-tree-select/demo/util.js b/components/vc-tree-select/demo/util.js
index 27fe44d06..6fb95eeb0 100644
--- a/components/vc-tree-select/demo/util.js
+++ b/components/vc-tree-select/demo/util.js
@@ -27,13 +27,15 @@ export function generateData (x = 3, y = 2, z = 1, gData = []) {
       tns[index].children = []
       return _loop(__level, key, tns[index].children)
     })
+
+    return null
   }
   _loop(z)
   return gData
 }
 export function calcTotal (x = 3, y = 2, z = 1) {
-  /* eslint no-param-reassign:0*/
-  const rec = (n) => n >= 0 ? x * Math.pow(y, n--) + rec(n) : 0
+  /* eslint no-param-reassign:0 */
+  const rec = (n) => n >= 0 ? x * (y ** (n--)) + rec(n) : 0
   return rec(z + 1)
 }
 console.log('总节点数(单个tree):', calcTotal())
diff --git a/components/vc-tree-select/index.js b/components/vc-tree-select/index.js
index f35201e54..8547ef4f9 100644
--- a/components/vc-tree-select/index.js
+++ b/components/vc-tree-select/index.js
@@ -1,7 +1,10 @@
-// rc-tree-select 1.12.13 tag
 // export this package's api
+// base 2.4.4
+import Vue from 'vue'
 import TreeSelect from './src'
+import ref from 'vue-ref'
 
+Vue.use(ref, { name: 'ant-ref' })
 export default TreeSelect
 
 export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './src'
diff --git a/components/vc-tree-select/src/Base/BasePopup.jsx b/components/vc-tree-select/src/Base/BasePopup.jsx
new file mode 100644
index 000000000..376cab6cb
--- /dev/null
+++ b/components/vc-tree-select/src/Base/BasePopup.jsx
@@ -0,0 +1,284 @@
+import warning from 'warning'
+import PropTypes from '../../../_util/vue-types'
+import { Tree } from '../../../vc-tree'
+import BaseMixin from '../../../_util/BaseMixin'
+
+// export const popupContextTypes = {
+//   onPopupKeyDown: PropTypes.func.isRequired,
+//   onTreeNodeSelect: PropTypes.func.isRequired,
+//   onTreeNodeCheck: PropTypes.func.isRequired,
+// }
+function getDerivedStateFromProps (nextProps, prevState) {
+  const { _prevProps: prevProps = {},
+    _loadedKeys: loadedKeys,
+    _expandedKeyList: expandedKeyList,
+    _cachedExpandedKeyList: cachedExpandedKeyList,
+  } = prevState || {}
+  const {
+    valueList, valueEntities, keyEntities,
+    treeExpandedKeys, filteredTreeNodes, searchValue,
+  } = nextProps
+
+  const newState = {
+    _prevProps: { ...nextProps },
+  }
+
+  // Check value update
+  if (valueList !== prevProps.valueList) {
+    newState._keyList = valueList
+      .map(({ value }) => valueEntities[value])
+      .filter(entity => entity)
+      .map(({ key }) => key)
+  }
+
+  // Show all when tree is in filter mode
+  if (
+    !treeExpandedKeys &&
+    filteredTreeNodes &&
+    filteredTreeNodes.length &&
+    filteredTreeNodes !== prevProps.filteredTreeNodes
+  ) {
+    newState._expandedKeyList = Object.keys(keyEntities)
+  }
+
+  // Cache `expandedKeyList` when filter set
+  if (searchValue && !prevProps.searchValue) {
+    newState._cachedExpandedKeyList = expandedKeyList
+  } else if (!searchValue && prevProps.searchValue && !treeExpandedKeys) {
+    newState._expandedKeyList = cachedExpandedKeyList || []
+    newState._cachedExpandedKeyList = []
+  }
+
+  // Use expandedKeys if provided
+  if (prevProps.treeExpandedKeys !== treeExpandedKeys) {
+    newState._expandedKeyList = treeExpandedKeys
+  }
+
+  // Clean loadedKeys if key not exist in keyEntities anymore
+  if (nextProps.loadData) {
+    newState._loadedKeys = loadedKeys.filter(key => key in keyEntities)
+  }
+
+  return newState
+}
+const BasePopup = {
+  mixins: [BaseMixin],
+  name: 'BasePopup',
+  props: {
+    prefixCls: PropTypes.string,
+    upperSearchValue: PropTypes.string,
+    valueList: PropTypes.array,
+    searchHalfCheckedKeys: PropTypes.array,
+    valueEntities: PropTypes.object,
+    keyEntities: PropTypes.object,
+    treeIcon: PropTypes.bool,
+    treeLine: PropTypes.bool,
+    treeNodeFilterProp: PropTypes.string,
+    treeCheckable: PropTypes.any,
+    treeCheckStrictly: PropTypes.bool,
+    treeDefaultExpandAll: PropTypes.bool,
+    treeDefaultExpandedKeys: PropTypes.array,
+    treeExpandedKeys: PropTypes.array,
+    loadData: PropTypes.func,
+    multiple: PropTypes.bool,
+    // onTreeExpand: PropTypes.func,
+    searchValue: PropTypes.string,
+    treeNodes: PropTypes.any,
+    filteredTreeNodes: PropTypes.any,
+    notFoundContent: PropTypes.string,
+
+    ariaId: PropTypes.string,
+    switcherIcon: PropTypes.any,
+    // HOC
+    renderSearch: PropTypes.func,
+    // onTreeExpanded: PropTypes.func,
+
+    __propsSymbol__: PropTypes.any,
+  },
+  inject: {
+    vcTreeSelect: { default: {}},
+  },
+  watch: {
+    __propsSymbol__ () {
+      const state = getDerivedStateFromProps(this.$props, this.$data)
+      this.setState(state)
+    },
+  },
+  data () {
+    warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__')
+    const {
+      treeDefaultExpandAll, treeDefaultExpandedKeys,
+      keyEntities,
+    } = this.$props
+
+    // TODO: make `expandedKeyList` control
+    let expandedKeyList = treeDefaultExpandedKeys
+    if (treeDefaultExpandAll) {
+      expandedKeyList = Object.keys(keyEntities)
+    }
+
+    const state = {
+      _keyList: [],
+      _expandedKeyList: expandedKeyList,
+      // Cache `expandedKeyList` when tree is in filter. This is used in `getDerivedStateFromProps`
+      _cachedExpandedKeyList: [], // eslint-disable-line react/no-unused-state
+      _loadedKeys: [],
+      _prevProps: {},
+    }
+    return {
+      ...state,
+      ...getDerivedStateFromProps(this.$props, state),
+    }
+  },
+  methods: {
+    onTreeExpand  (expandedKeyList) {
+      const { treeExpandedKeys } = this.$props
+
+      // Set uncontrolled state
+      if (!treeExpandedKeys) {
+        this.setState({ _expandedKeyList: expandedKeyList }, () => {
+          this.__emit('treeExpanded')
+        })
+      }
+      this.__emit('treeExpand', expandedKeyList)
+    },
+
+    onLoad  (loadedKeys) {
+      this.setState({ _loadedKeys: loadedKeys })
+    },
+
+    /**
+     * Not pass `loadData` when searching. To avoid loop ajax call makes browser crash.
+     */
+    getLoadData  () {
+      const { loadData, searchValue } = this.$props
+      if (searchValue) return null
+      return loadData
+    },
+
+    /**
+     * This method pass to Tree component which is used for add filtered class
+     * in TreeNode > li
+     */
+    filterTreeNode  (treeNode) {
+      const { upperSearchValue, treeNodeFilterProp } = this.$props
+
+      const filterVal = treeNode[treeNodeFilterProp]
+      if (typeof filterVal === 'string') {
+        return upperSearchValue && (filterVal).toUpperCase().indexOf(upperSearchValue) !== -1
+      }
+
+      return false
+    },
+
+    renderNotFound  () {
+      const { prefixCls, notFoundContent } = this.$props
+
+      return (
+        <span class={`${prefixCls}-not-found`}>
+          {notFoundContent}
+        </span>
+      )
+    },
+  },
+
+  render () {
+    const { _keyList: keyList, _expandedKeyList: expandedKeyList, _loadedKeys: loadedKeys } = this.$data
+    const {
+      prefixCls,
+      treeNodes, filteredTreeNodes,
+      treeIcon, treeLine, treeCheckable, treeCheckStrictly, multiple,
+      ariaId,
+      renderSearch,
+      switcherIcon,
+      searchHalfCheckedKeys,
+    } = this.$props
+    const { vcTreeSelect: {
+      onPopupKeyDown,
+      onTreeNodeSelect,
+      onTreeNodeCheck,
+    }} = this
+
+    const loadData = this.getLoadData()
+
+    const treeProps = {}
+
+    if (treeCheckable) {
+      treeProps.checkedKeys = keyList
+    } else {
+      treeProps.selectedKeys = keyList
+    }
+    let $notFound
+    let $treeNodes
+    if (filteredTreeNodes) {
+      if (filteredTreeNodes.length) {
+        treeProps.checkStrictly = true
+        $treeNodes = filteredTreeNodes
+
+        // Fill halfCheckedKeys
+        if (treeCheckable && !treeCheckStrictly) {
+          treeProps.checkedKeys = {
+            checked: keyList,
+            halfChecked: searchHalfCheckedKeys,
+          }
+        }
+      } else {
+        $notFound = this.renderNotFound()
+      }
+    } else if (!treeNodes.length) {
+      $notFound = this.renderNotFound()
+    } else {
+      $treeNodes = treeNodes
+    }
+
+    let $tree
+    if ($notFound) {
+      $tree = $notFound
+    } else {
+      const treeAllProps = {
+        props: {
+          prefixCls: `${prefixCls}-tree`,
+          showIcon: treeIcon,
+          showLine: treeLine,
+          selectable: !treeCheckable,
+          checkable: treeCheckable,
+          checkStrictly: treeCheckStrictly,
+          multiple: multiple,
+          loadData: loadData,
+          loadedKeys: loadedKeys,
+          expandedKeys: expandedKeyList,
+          filterTreeNode: this.filterTreeNode,
+          switcherIcon: switcherIcon,
+          ...treeProps,
+          __propsSymbol__: Symbol(),
+          children: $treeNodes,
+        },
+        on: {
+          select: onTreeNodeSelect,
+          check: onTreeNodeCheck,
+          expand: this.onTreeExpand,
+          load: this.onLoad,
+        },
+      }
+      $tree = (
+        <Tree
+          {...treeAllProps}
+        />
+      )
+    }
+
+    return (
+      <div
+        role='listbox'
+        id={ariaId}
+        onKeydown={onPopupKeyDown}
+        tabIndex={-1}
+      >
+        {renderSearch ? renderSearch() : null}
+        {$tree}
+      </div>
+    )
+  },
+}
+
+export default BasePopup
diff --git a/components/vc-tree-select/src/Base/BaseSelector.jsx b/components/vc-tree-select/src/Base/BaseSelector.jsx
new file mode 100644
index 000000000..4580b4570
--- /dev/null
+++ b/components/vc-tree-select/src/Base/BaseSelector.jsx
@@ -0,0 +1,187 @@
+/**
+ * Input Box is in different position for different mode.
+ * This not the same design as `Select` cause it's followed by antd 0.x `Select`.
+ * We will not follow the new design immediately since antd 3.x is already released.
+ *
+ * So this file named as Selector to avoid confuse.
+ */
+import { createRef } from '../util'
+import PropTypes from '../../../_util/vue-types'
+import classNames from 'classnames'
+import { initDefaultProps, getComponentFromProp } from '../../../_util/props-util'
+import BaseMixin from '../../../_util/BaseMixin'
+export const selectorPropTypes = () => ({
+  prefixCls: PropTypes.string,
+  className: PropTypes.string,
+  open: PropTypes.bool,
+  valueList: PropTypes.array, // Name as valueList to diff the single value
+  allowClear: PropTypes.bool,
+  showArrow: PropTypes.bool,
+  // onClick: PropTypes.func,
+  // onBlur: PropTypes.func,
+  // onFocus: PropTypes.func,
+  removeSelected: PropTypes.func,
+  choiceTransitionName: PropTypes.string,
+  // Pass by component
+  ariaId: PropTypes.string,
+  inputIcon: PropTypes.any,
+  clearIcon: PropTypes.any,
+  removeIcon: PropTypes.any,
+  selectorValueList: PropTypes.array,
+  placeholder: PropTypes.any,
+  disabled: PropTypes.bool,
+  focused: PropTypes.bool,
+})
+
+function noop () {}
+export default function (modeName) {
+  const BaseSelector = {
+    name: 'BaseSelector',
+    mixins: [BaseMixin],
+    props: initDefaultProps({
+      ...selectorPropTypes(),
+
+      // Pass by HOC
+      renderSelection: PropTypes.func.isRequired,
+      renderPlaceholder: PropTypes.func,
+      tabIndex: PropTypes.number,
+    }, {
+      tabIndex: 0,
+    }),
+    inject: {
+      vcTreeSelect: { default: {}},
+    },
+    created () {
+      this.domRef = createRef()
+    },
+    methods: {
+      onFocus  (e) {
+        const { focused } = this.$props
+        const { vcTreeSelect: { onSelectorFocus }} = this
+
+        if (!focused) {
+          onSelectorFocus()
+        }
+        this.__emit('focus', e)
+      },
+
+      onBlur (e) {
+        const { vcTreeSelect: { onSelectorBlur }} = this
+
+        // TODO: Not trigger when is inner component get focused
+        onSelectorBlur()
+        this.__emit('blur', e)
+      },
+
+      focus  () {
+        this.domRef.current.focus()
+      },
+
+      blur  () {
+        this.domRef.current.blur()
+      },
+
+      renderClear () {
+        const { prefixCls, allowClear, valueList } = this.$props
+        const { vcTreeSelect: { onSelectorClear }} = this
+
+        if (!allowClear || !valueList.length || !valueList[0].value) {
+          return null
+        }
+        const clearIcon = getComponentFromProp(this, 'clearIcon')
+        return (
+          <span
+            key='clear'
+            class={`${prefixCls}-selection__clear`}
+            onClick={onSelectorClear}
+          >
+            {clearIcon}
+          </span>
+        )
+      },
+
+      renderArrow () {
+        const { prefixCls, showArrow } = this.$props
+        if (!showArrow) {
+          return null
+        }
+        const inputIcon = getComponentFromProp(this, 'inputIcon')
+        return (
+          <span
+            key='arrow'
+            class={`${prefixCls}-arrow`}
+            style={{ outline: 'none' }}
+          >
+            {inputIcon}
+          </span>
+        )
+      },
+    },
+
+    render () {
+      const {
+        prefixCls, className, style,
+        open, focused, disabled, allowClear,
+        ariaId,
+        renderSelection, renderPlaceholder,
+        tabIndex,
+      } = this.$props
+      const { vcTreeSelect: { onSelectorKeyDown }, $listeners } = this
+
+      let myTabIndex = tabIndex
+      if (disabled) {
+        myTabIndex = null
+      }
+
+      return (
+        <span
+          style={style}
+          onClick={$listeners.click || noop}
+          class={classNames(
+            className,
+            prefixCls,
+            {
+              [`${prefixCls}-open`]: open,
+              [`${prefixCls}-focused`]: open || focused,
+              [`${prefixCls}-disabled`]: disabled,
+              [`${prefixCls}-enabled`]: !disabled,
+              [`${prefixCls}-allow-clear`]: allowClear,
+            }
+          )}
+          {...{
+            directives: [{
+              name: 'ant-ref',
+              value: this.domRef,
+            }],
+          }}
+          role='combobox'
+          aria-expanded={open}
+          aria-owns={open ? ariaId : undefined}
+          aria-controls={open ? ariaId : undefined}
+          aria-haspopup='listbox'
+          aria-disabled={disabled}
+          tabIndex={myTabIndex}
+          onFocus={this.onFocus}
+          onBlur={this.onBlur}
+          onKeydown={onSelectorKeyDown}
+        >
+          <span
+            key='selection'
+            class={classNames(
+              `${prefixCls}-selection`,
+              `${prefixCls}-selection--${modeName}`
+            )}
+          >
+            {renderSelection()}
+            {this.renderClear()}
+            {this.renderArrow()}
+
+            {renderPlaceholder && renderPlaceholder()}
+          </span>
+        </span>
+      )
+    },
+  }
+
+  return BaseSelector
+}
diff --git a/components/vc-tree-select/src/Popup/MultiplePopup.jsx b/components/vc-tree-select/src/Popup/MultiplePopup.jsx
new file mode 100644
index 000000000..8f68c4e22
--- /dev/null
+++ b/components/vc-tree-select/src/Popup/MultiplePopup.jsx
@@ -0,0 +1,3 @@
+import BasePopup from '../Base/BasePopup'
+
+export default BasePopup
diff --git a/components/vc-tree-select/src/Popup/SinglePopup.jsx b/components/vc-tree-select/src/Popup/SinglePopup.jsx
new file mode 100644
index 000000000..e72c7b896
--- /dev/null
+++ b/components/vc-tree-select/src/Popup/SinglePopup.jsx
@@ -0,0 +1,80 @@
+import PropTypes from '../../../_util/vue-types'
+import BasePopup from '../Base/BasePopup'
+import SearchInput from '../SearchInput'
+import { createRef } from '../util'
+
+const SinglePopup = {
+  name: 'SinglePopup',
+  props: {
+    ...BasePopup.props,
+    ...SearchInput.props,
+    searchValue: PropTypes.string,
+    showSearch: PropTypes.bool,
+    dropdownPrefixCls: PropTypes.string,
+    disabled: PropTypes.bool,
+    searchPlaceholder: PropTypes.string,
+  },
+  created () {
+    this.inputRef = createRef()
+  },
+  methods: {
+    onPlaceholderClick  () {
+      this.inputRef.current.focus()
+    },
+
+    _renderPlaceholder  () {
+      const { searchPlaceholder, searchValue, prefixCls } = this.$props
+
+      if (!searchPlaceholder) {
+        return null
+      }
+
+      return (
+        <span
+          style={{
+            display: searchValue ? 'none' : 'block',
+          }}
+          onClick={this.onPlaceholderClick}
+          class={`${prefixCls}-search__field__placeholder`}
+        >
+          {searchPlaceholder}
+        </span>
+      )
+    },
+
+    _renderSearch () {
+      const { showSearch, dropdownPrefixCls } = this.$props
+
+      if (!showSearch) {
+        return null
+      }
+
+      return (
+        <span class={`${dropdownPrefixCls}-search`}>
+          <SearchInput
+            {...{
+              props: { ...this.$props, renderPlaceholder: this._renderPlaceholder },
+              on: this.$listeners,
+              directives: [{
+                name: 'ant-ref',
+                value: this.inputRef,
+              }],
+            }}
+          />
+        </span>
+      )
+    },
+  },
+  render () {
+    return (
+      <BasePopup
+        {...{
+          props: { ...this.$props, renderSearch: this._renderSearch, __propsSymbol__: Symbol() },
+          on: this.$listeners,
+        }}
+      />
+    )
+  },
+}
+
+export default SinglePopup
diff --git a/components/vc-tree-select/src/PropTypes.js b/components/vc-tree-select/src/PropTypes.js
index e6495bb03..2afe88e41 100644
--- a/components/vc-tree-select/src/PropTypes.js
+++ b/components/vc-tree-select/src/PropTypes.js
@@ -1,62 +1,46 @@
 import PropTypes from '../../_util/vue-types'
-import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
+import { isLabelInValue } from './util'
 
-export const SelectPropTypes = {
-  // className: PropTypes.string,
-  prefixCls: PropTypes.string,
-  multiple: PropTypes.bool,
-  filterTreeNode: PropTypes.any,
-  showSearch: PropTypes.bool,
-  disabled: PropTypes.bool,
-  showArrow: PropTypes.bool,
-  allowClear: PropTypes.bool,
-  defaultOpen: PropTypes.bool,
-  open: PropTypes.bool,
-  transitionName: PropTypes.string,
-  animation: PropTypes.string,
-  choiceTransitionName: PropTypes.string,
-  // onClick: PropTypes.func,
-  // onChange: PropTypes.func,
-  // onSelect: PropTypes.func,
-  // onDeselect: PropTypes.func,
-  // onSearch: PropTypes.func,
-  searchPlaceholder: PropTypes.string,
-  placeholder: PropTypes.any,
-  inputValue: PropTypes.any,
-  value: PropTypes.any,
-  defaultValue: PropTypes.any,
-  label: PropTypes.any, // vnode
-  defaultLabel: PropTypes.any,
-  labelInValue: PropTypes.bool,
-  dropdownClassName: PropTypes.string,
-  dropdownStyle: PropTypes.object,
-  dropdownPopupAlign: PropTypes.object,
-  dropdownVisibleChange: PropTypes.func,
-  maxTagTextLength: PropTypes.number,
-  showCheckedStrategy: PropTypes.oneOf([
-    SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
-  ]),
-  treeCheckStrictly: PropTypes.bool,
-  treeIcon: PropTypes.bool,
-  treeLine: PropTypes.bool,
-  treeDefaultExpandAll: PropTypes.bool,
-  treeDefaultExpandedKeys: PropTypes.arrayOf(String),
-  treeCheckable: PropTypes.any, // bool vnode
-  treeNodeLabelProp: PropTypes.string,
-  treeNodeFilterProp: PropTypes.string,
-  treeData: PropTypes.array,
-  treeDataSimpleMode: PropTypes.oneOfType([
-    PropTypes.bool,
-    PropTypes.object,
-  ]),
-  loadData: PropTypes.func,
-  dropdownMatchSelectWidth: PropTypes.bool,
-  notFoundContent: PropTypes.any,
-  children: PropTypes.any,
-  autoFocus: PropTypes.bool,
-  getPopupContainer: PropTypes.func,
-  switcherIcon: PropTypes.func,
-  inputIcon: PropTypes.any,
-  removeIcon: PropTypes.any,
-  clearIcon: PropTypes.any,
+const internalValProp = PropTypes.oneOfType([
+  PropTypes.string,
+  PropTypes.number,
+])
+
+export function genArrProps (propType) {
+  return PropTypes.oneOfType([
+    propType,
+    PropTypes.arrayOf(propType),
+  ])
+}
+
+/**
+ * Origin code check `multiple` is true when `treeCheckStrictly` & `labelInValue`.
+ * But in process logic is already cover to array.
+ * Check array is not necessary. Let's simplify this check logic.
+ */
+export function valueProp (...args) {
+  const [props, propName, Component] = args
+
+  if (isLabelInValue(props)) {
+    const err = genArrProps(PropTypes.shape({
+      label: PropTypes.node,
+      value: internalValProp,
+    }).loose)(...args)
+    if (err) {
+      return new Error(
+        `Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
+        `You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.`
+      )
+    }
+    return null
+  }
+
+  const err = genArrProps(internalValProp)(...args)
+  if (err) {
+    return new Error(
+      `Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
+      `You should use string or [string] instead.`
+    )
+  }
+  return null
 }
diff --git a/components/vc-tree-select/src/SearchInput.jsx b/components/vc-tree-select/src/SearchInput.jsx
new file mode 100644
index 000000000..c682e5b8e
--- /dev/null
+++ b/components/vc-tree-select/src/SearchInput.jsx
@@ -0,0 +1,127 @@
+/**
+ * Since search box is in different position with different mode.
+ * - Single: in the popup box
+ * - multiple: in the selector
+ * Move the code as a SearchInput for easy management.
+ */
+
+import PropTypes from '../../_util/vue-types'
+import { createRef } from './util'
+
+const SearchInput = {
+  name: 'SearchInput',
+  props: {
+    open: PropTypes.bool,
+    searchValue: PropTypes.string,
+    prefixCls: PropTypes.string,
+    disabled: PropTypes.bool,
+    renderPlaceholder: PropTypes.func,
+    needAlign: PropTypes.bool,
+    ariaId: PropTypes.string,
+  },
+  inject: {
+    vcTreeSelect: { default: {}},
+  },
+
+  created () {
+    this.inputRef = createRef()
+    this.mirrorInputRef = createRef()
+    this.prevProps = { ...this.$props }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      const { open, needAlign } = this.$props
+      if (needAlign) {
+        this.alignInputWidth()
+      }
+
+      if (open) {
+        this.focus(true)
+      }
+    })
+  },
+
+  updated () {
+    const { open, searchValue, needAlign } = this.$props
+    const { prevProps } = this
+    this.$nextTick(() => {
+      if (open && prevProps.open !== open) {
+        this.focus()
+      }
+      if (needAlign && searchValue !== prevProps.searchValue) {
+        this.alignInputWidth()
+      }
+      this.prevProps = { ...this.$props }
+    })
+  },
+  methods: {
+    /**
+   * `scrollWidth` is not correct in IE, do the workaround.
+   * ref: https://github.com/react-component/tree-select/issues/65
+   *  clientWidth 0 when mounted in vue. why?
+   */
+    alignInputWidth () {
+      this.inputRef.current.style.width =
+      `${this.mirrorInputRef.current.clientWidth || this.mirrorInputRef.current.offsetWidth}px`
+    },
+
+    /**
+   * Need additional timeout for focus cause parent dom is not ready when didMount trigger
+   */
+    focus (isDidMount) {
+      if (this.inputRef.current) {
+        this.inputRef.current.focus()
+        if (isDidMount) {
+          setTimeout(() => {
+            this.inputRef.current.focus()
+          }, 0)
+        }
+      }
+    },
+
+    blur () {
+      if (this.inputRef.current) {
+        this.inputRef.current.blur()
+      }
+    },
+  },
+
+  render () {
+    const { searchValue, prefixCls, disabled, renderPlaceholder, open, ariaId } = this.$props
+    const { vcTreeSelect: {
+      onSearchInputChange, onSearchInputKeyDown,
+    }} = this
+    return (
+      <span class={`${prefixCls}-search__field__wrap`}>
+        <input
+          type='text'
+          {...{ directives: [{
+            name: 'ant-ref',
+            value: this.inputRef,
+          }] }}
+          onInput={onSearchInputChange}
+          onKeydown={onSearchInputKeyDown}
+          value={searchValue}
+          disabled={disabled}
+          class={`${prefixCls}-search__field`}
+          aria-label='filter select'
+          aria-autocomplete='list'
+          aria-controls={open ? ariaId : undefined}
+          aria-multiline='false'
+        />
+        <span
+          {...{ directives: [{
+            name: 'ant-ref',
+            value: this.mirrorInputRef,
+          }] }}
+          class={`${prefixCls}-search__field__mirror`}
+        >
+          {searchValue}&nbsp;
+        </span>
+        {renderPlaceholder ? renderPlaceholder() : null}
+      </span>
+    )
+  },
+}
+
+export default SearchInput
diff --git a/components/vc-tree-select/src/Select.jsx b/components/vc-tree-select/src/Select.jsx
index b450941de..935d1d9de 100644
--- a/components/vc-tree-select/src/Select.jsx
+++ b/components/vc-tree-select/src/Select.jsx
@@ -1,398 +1,800 @@
+/**
+ * ARIA: https://www.w3.org/TR/wai-aria/#combobox
+ * Sample 1: https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/examples/combobox/aria1.1pattern/listbox-combo.html
+ * Sample 2: https://www.w3.org/blog/wai-components-gallery/widget/combobox-with-aria-autocompleteinline/
+ *
+ * Tab logic:
+ * Popup is close
+ * 1. Focus input (mark component as focused)
+ * 2. Press enter to show the popup
+ * 3. If popup has input, focus it
+ *
+ * Popup is open
+ * 1. press tab to close the popup
+ * 2. Focus back to the selection input box
+ * 3. Let the native tab going on
+ *
+ * TreeSelect use 2 design type.
+ * In single mode, we should focus on the `span`
+ * In multiple mode, we should focus on the `input`
+ */
+
+import shallowEqual from 'shallowequal'
+import raf from 'raf'
+import warning from 'warning'
 import PropTypes from '../../_util/vue-types'
 import KeyCode from '../../_util/KeyCode'
-import classnames from 'classnames'
-import pick from 'lodash/pick'
-import omit from 'omit.js'
-import {
-  getPropValue, getValuePropValue,
-  isMultiple, toArray,
-  UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE,
-  preventDefaultEvent,
-  getTreeNodesStates, flatToHierarchy, filterParentPosition,
-  isPositionPrefix, labelCompatible, loopAllChildren, filterAllCheckedData,
-  processSimpleTreeData, toTitle,
-} from './util'
+
 import SelectTrigger from './SelectTrigger'
-import _TreeNode from './TreeNode'
+import SingleSelector from './Selector/SingleSelector'
+import MultipleSelector from './Selector/MultipleSelector'
+import SinglePopup from './Popup/SinglePopup'
+import MultiplePopup from './Popup/MultiplePopup'
+
 import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
-import { SelectPropTypes } from './PropTypes'
-import { initDefaultProps, getOptionProps, hasProp, getAllProps, getComponentFromProp } from '../../_util/props-util'
 import BaseMixin from '../../_util/BaseMixin'
-import getTransitionProps from '../../_util/getTransitionProps'
-
-function filterFn (input, child) {
-  return String(getPropValue(child, labelCompatible(this.$props.treeNodeFilterProp)))
-    .indexOf(input) > -1
-}
-
-const defaultProps = {
-  prefixCls: 'rc-tree-select',
-  // filterTreeNode: filterFn, // [Legacy] TODO: Set false and filter not hide?
-  showSearch: true,
-  allowClear: false,
-  // placeholder: '',
-  // searchPlaceholder: '',
-  labelInValue: false,
-  // onClick: noop,
-  // onChange: noop,
-  // onSelect: noop,
-  // onDeselect: noop,
-  // onSearch: noop,
-  showArrow: true,
-  dropdownMatchSelectWidth: true,
-  dropdownStyle: {},
-  dropdownVisibleChange: () => { return true },
-  notFoundContent: 'Not Found',
-  showCheckedStrategy: SHOW_CHILD,
-  // skipHandleInitValue: false, // Deprecated (use treeCheckStrictly)
-  treeCheckStrictly: false,
-  treeIcon: false,
-  treeLine: false,
-  treeDataSimpleMode: false,
-  treeDefaultExpandAll: false,
-  treeCheckable: false,
-  treeNodeFilterProp: 'value',
-  treeNodeLabelProp: 'title',
-}
-
-const Select = {
-  mixins: [BaseMixin],
-  name: 'VCTreeSelect',
-  props: initDefaultProps({ ...SelectPropTypes, __propsSymbol__: PropTypes.any }, defaultProps),
-  data () {
-    let value = []
-    const props = getOptionProps(this)
-    this.preProps = { ...props }
-    if ('value' in props) {
-      value = toArray(props.value)
-    } else {
-      value = toArray(props.defaultValue)
+import {
+  createRef, generateAriaId,
+  formatInternalValue, formatSelectorValue,
+  parseSimpleTreeData,
+  convertDataToTree, convertTreeToEntities, conductCheck,
+  getHalfCheckedKeys,
+  flatToHierarchy,
+  isPosRelated, isLabelInValue, getFilterTree,
+  cleanEntity,
+} from './util'
+import SelectNode from './SelectNode'
+import { initDefaultProps, getOptionProps, mergeProps, getPropsData } from '../../_util/props-util'
+function getWatch (keys = []) {
+  const watch = {}
+  keys.forEach(k => {
+    watch[k] = function () {
+      this.needSyncKeys[k] = true
     }
-    // save parsed treeData, for performance (treeData may be very big)
-    this.renderedTreeData = this.renderTreeData()
-    value = this.addLabelToValue(props, value)
-    value = this.getValue(props, value, props.inputValue ? '__strict' : true)
-    const inputValue = props.inputValue || ''
-    // if (props.combobox) {
-    //   inputValue = value.length ? String(value[0].value) : '';
-    // }
+  })
+  return watch
+}
+const Select = {
+  name: 'Select',
+  mixins: [BaseMixin],
+  props: initDefaultProps({
+    prefixCls: PropTypes.string,
+    prefixAria: PropTypes.string,
+    multiple: PropTypes.bool,
+    showArrow: PropTypes.bool,
+    open: PropTypes.bool,
+    value: PropTypes.any,
+
+    autoFocus: PropTypes.bool,
+
+    defaultOpen: PropTypes.bool,
+    defaultValue: PropTypes.any,
+
+    showSearch: PropTypes.bool,
+    placeholder: PropTypes.any,
+    inputValue: PropTypes.string, // [Legacy] Deprecated. Use `searchValue` instead.
+    searchValue: PropTypes.string,
+    autoClearSearchValue: PropTypes.bool,
+    searchPlaceholder: PropTypes.any, // [Legacy] Confuse with placeholder
+    disabled: PropTypes.bool,
+    children: PropTypes.any,
+    labelInValue: PropTypes.bool,
+    maxTagCount: PropTypes.number,
+    maxTagPlaceholder: PropTypes.any,
+    maxTagTextLength: PropTypes.number,
+    showCheckedStrategy: PropTypes.oneOf([
+      SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
+    ]),
+    dropdownClassName: PropTypes.string,
+    dropdownStyle: PropTypes.object,
+    dropdownVisibleChange: PropTypes.func,
+    dropdownMatchSelectWidth: PropTypes.bool,
+    treeData: PropTypes.array,
+    treeDataSimpleMode: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
+    treeNodeFilterProp: PropTypes.string,
+    treeNodeLabelProp: PropTypes.string,
+    treeCheckable: PropTypes.any,
+    treeCheckStrictly: PropTypes.bool,
+    treeIcon: PropTypes.bool,
+    treeLine: PropTypes.bool,
+    treeDefaultExpandAll: PropTypes.bool,
+    treeDefaultExpandedKeys: PropTypes.array,
+    treeExpandedKeys: PropTypes.array,
+    loadData: PropTypes.func,
+    filterTreeNode: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
+
+    notFoundContent: PropTypes.any,
+    getPopupContainer: PropTypes.func,
+
+    // onSearch: PropTypes.func,
+    // onSelect: PropTypes.func,
+    // onDeselect: PropTypes.func,
+    // onChange: PropTypes.func,
+    // onDropdownVisibleChange: PropTypes.func,
+
+    // onTreeExpand: PropTypes.func,
+    allowClear: PropTypes.bool,
+    transitionName: PropTypes.string,
+    animation: PropTypes.string,
+    choiceTransitionName: PropTypes.string,
+    inputIcon: PropTypes.any,
+    clearIcon: PropTypes.any,
+    removeIcon: PropTypes.any,
+    switcherIcon: PropTypes.any,
+    __propsSymbol__: PropTypes.any,
+  }, {
+    prefixCls: 'rc-tree-select',
+    prefixAria: 'rc-tree-select',
+    showArrow: true,
+    showSearch: true,
+    autoClearSearchValue: true,
+    showCheckedStrategy: SHOW_CHILD,
+
+    // dropdownMatchSelectWidth change the origin design, set to false now
+    // ref: https://github.com/react-component/select/blob/4cad95e098a341a09de239ad6981067188842020/src/Select.jsx#L344
+    // ref: https://github.com/react-component/select/pull/71
+    treeNodeFilterProp: 'value',
+    treeNodeLabelProp: 'title',
+    treeIcon: false,
+    notFoundContent: 'Not Found',
+    dropdownStyle: {},
+    dropdownVisibleChange: () => { return true },
+  }),
+
+  data () {
+    warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__')
+    const {
+      prefixAria,
+      defaultOpen, open,
+    } = this.$props
+    this.needSyncKeys = {}
+    this.selectorRef = createRef()
+    this.selectTriggerRef = createRef()
+
+    // ARIA need `aria-controls` props mapping
+    // Since this need user input. Let's generate ourselves
+    this.ariaId = generateAriaId(`${prefixAria}-list`)
+
+    const state = {
+      _open: open || defaultOpen,
+      _valueList: [],
+      _searchHalfCheckedKeys: [],
+      _missValueList: [], // Contains the value not in the tree
+      _selectorValueList: [], // Used for multiple selector
+      _valueEntities: {},
+      _keyEntities: {},
+      _searchValue: '',
+      _prevProps: {},
+      _init: true,
+      _focused: undefined,
+      _treeNodes: undefined,
+      _filteredTreeNodes: undefined,
+    }
+    const newState = this.getDerivedStateFromProps(this.$props, state)
     return {
-      sValue: value,
-      sInputValue: inputValue,
-      sOpen: props.open || props.defaultOpen,
-      sFocused: false,
+      ...state,
+      ...newState,
     }
   },
 
+  provide () {
+    return {
+      vcTreeSelect: {
+        onSelectorFocus: this.onSelectorFocus,
+        onSelectorBlur: this.onSelectorBlur,
+        onSelectorKeyDown: this.onComponentKeyDown,
+        onSelectorClear: this.onSelectorClear,
+        onMultipleSelectorRemove: this.onMultipleSelectorRemove,
+
+        onTreeNodeSelect: this.onTreeNodeSelect,
+        onTreeNodeCheck: this.onTreeNodeCheck,
+        onPopupKeyDown: this.onComponentKeyDown,
+
+        onSearchInputChange: this.onSearchInputChange,
+        onSearchInputKeyDown: this.onSearchInputKeyDown,
+      },
+    }
+  },
+  watch: {
+    ...getWatch(['treeData', 'defaultValue', 'value']),
+    __propsSymbol__ () {
+      const state = this.getDerivedStateFromProps(this.$props, this.$data)
+      this.setState(state)
+      this.needSyncKeys = {}
+    },
+    '$data._valueList': function () {
+      this.$nextTick(() => {
+        this.forcePopupAlign()
+      })
+    },
+  },
   mounted () {
     this.$nextTick(() => {
-      const { autoFocus, disabled } = this
-      if (isMultiple(this.$props)) {
-        const inputNode = this.getInputDOMNode()
-        if (inputNode.value) {
-          inputNode.style.width = ''
-          inputNode.style.width = `${this.$refs.inputMirrorInstance.clientWidth || this.$refs.inputMirrorInstance.offsetWidth}px`
-        } else {
-          inputNode.style.width = ''
-        }
-      }
+      const { autoFocus, disabled } = this.$props
       if (autoFocus && !disabled) {
         this.focus()
       }
     })
   },
-  watch: {
-    // for performance (use __propsSymbol__ avoid deep watch)
-    __propsSymbol__ () {
-      const nextProps = getOptionProps(this)
-      // save parsed treeData, for performance (treeData may be very big)
-      this.renderedTreeData = this.renderTreeData(nextProps)
-      // Detecting whether the object of `onChange`'s argument  is old ref.
-      // Better to do a deep equal later.
-      this._cacheTreeNodesStates = this._cacheTreeNodesStates !== 'no' &&
-                                 this._savedValue &&
-                                 nextProps.value === this._savedValue
-      if (this.preProps.treeData !== nextProps.treeData ||
-      this.preProps.children !== nextProps.children) {
-      // refresh this._treeNodesStates cache
-        this._treeNodesStates = getTreeNodesStates(
-          this.renderedTreeData || nextProps.children,
-          this.sValue.map(item => item.value)
+
+  methods: {
+    getDerivedStateFromProps (nextProps, prevState) {
+      const h = this.$createElement
+      const { _prevProps: prevProps = {}} = prevState
+      const {
+        treeCheckable, treeCheckStrictly,
+        filterTreeNode, treeNodeFilterProp,
+        treeDataSimpleMode,
+      } = nextProps
+      const newState = {
+        _prevProps: { ...nextProps },
+        _init: false,
+      }
+      const self = this
+      // Process the state when props updated
+      function processState (propName, updater) {
+        if (prevProps[propName] !== nextProps[propName] || self.needSyncKeys[propName]) {
+          updater(nextProps[propName], prevProps[propName])
+          return true
+        }
+        return false
+      }
+
+      let valueRefresh = false
+
+      // Open
+      processState('open', (propValue) => {
+        newState._open = propValue
+      })
+
+      // Tree Nodes
+      let treeNodes
+      let treeDataChanged = false
+      let treeDataModeChanged = false
+      processState('treeData', (propValue) => {
+        treeNodes = convertDataToTree(h, propValue)
+        treeDataChanged = true
+      })
+
+      processState('treeDataSimpleMode', (propValue, prevValue) => {
+        if (!propValue) return
+
+        const prev = !prevValue || prevValue === true ? {} : prevValue
+
+        // Shallow equal to avoid dynamic prop object
+        if (!shallowEqual(propValue, prev)) {
+          treeDataModeChanged = true
+        }
+      })
+
+      // Parse by `treeDataSimpleMode`
+      if (treeDataSimpleMode && (treeDataChanged || treeDataModeChanged)) {
+        const simpleMapper = {
+          id: 'id',
+          pId: 'pId',
+          rootPId: null,
+          ...(treeDataSimpleMode !== true ? treeDataSimpleMode : {}),
+        }
+        treeNodes = convertDataToTree(
+          h,
+          parseSimpleTreeData(nextProps.treeData, simpleMapper)
         )
       }
-      if ('value' in nextProps) {
-        let value = toArray(nextProps.value)
-        value = this.addLabelToValue(nextProps, value)
-        value = this.getValue(nextProps, value)
-        this.setState({
-          sValue: value,
-        }, this.forcePopupAlign)
-      // if (nextProps.combobox) {
-      //   this.setState({
-      //     inputValue: value.length ? String(value[0].key) : '',
-      //   });
-      // }
+
+      // If `treeData` not provide, use children TreeNodes
+      if (!nextProps.treeData) {
+        // processState('children', (propValue) => {
+        //   treeNodes = Array.isArray(propValue) ? propValue : [propValue]
+        // })
+        treeNodes = this.$slots.default
       }
-      if (nextProps.inputValue !== this.preProps.inputValue) {
-        this.setState({
-          sInputValue: nextProps.inputValue,
+
+      // Convert `treeData` to entities
+      if (treeNodes) {
+        const entitiesMap = convertTreeToEntities(treeNodes)
+        newState._treeNodes = treeNodes
+        newState._posEntities = entitiesMap.posEntities
+        newState._valueEntities = entitiesMap.valueEntities
+        newState._keyEntities = entitiesMap.keyEntities
+
+        valueRefresh = true
+      }
+
+      // Value List
+      if (prevState._init) {
+        processState('defaultValue', (propValue) => {
+          newState._valueList = formatInternalValue(propValue, nextProps)
+          valueRefresh = true
         })
       }
-      if ('open' in nextProps) {
-        this.setState({
-          sOpen: nextProps.open,
-        })
-      }
-      this.preProps = { ...nextProps }
-    },
-  },
 
-  beforeUpdate () {
-    if (this._savedValue && this.$props.value &&
-      this.$props.value !== this._savedValue &&
-      this.$props.value === this.preProps.value) {
-      this._cacheTreeNodesStates = false
-      this.getValue(this.$props, this.addLabelToValue(this.$props, toArray(this.$props.value)))
-    }
-  },
-
-  updated () {
-    const state = this.$data
-    const props = this.$props
-    if (state.sOpen && isMultiple(props)) {
-      this.$nextTick(() => {
-        const inputNode = this.getInputDOMNode()
-        if (inputNode.value) {
-          inputNode.style.width = ''
-          inputNode.style.width = `${this.$refs.inputMirrorInstance.clientWidth}px`
-        } else {
-          inputNode.style.width = ''
-        }
+      processState('value', (propValue) => {
+        newState._valueList = formatInternalValue(propValue, nextProps)
+        valueRefresh = true
       })
-    }
-  },
 
-  beforeDestroy () {
-    this.clearDelayTimer()
-    if (this.dropdownContainer) {
-      document.body.removeChild(this.dropdownContainer)
-      this.dropdownContainer = null
-    }
-  },
-  methods: {
-    loopTreeData (data, level = 0, treeCheckable) {
-      return data.map((item, index) => {
-        const pos = `${level}-${index}`
-        const {
-          label,
-          value,
-          disabled,
-          key,
-          selectable,
-          children,
-          isLeaf,
-          ...otherProps
-        } = item
-        const tnProps = {
-          ...pick(item, ['on', 'class', 'style']),
-          props: {
-            value,
-            title: label,
-            disabled: disabled || false,
-            selectable: selectable === false ? selectable : !treeCheckable,
-            ...omit(otherProps, ['on', 'class', 'style']),
-          },
-          key: key || value || pos,
-        }
-        let ret
-        if (children && children.length) {
-          ret = (<_TreeNode {...tnProps}>{this.loopTreeData(children, pos, treeCheckable)}</_TreeNode>)
-        } else {
-          ret = (<_TreeNode {...tnProps} isLeaf={isLeaf}/>)
-        }
-        return ret
-      })
-    },
-    onInputChange (event) {
-      const val = event.target.value
-      const { $props: props } = this
-      this.setState({
-        sInputValue: val,
-        sOpen: true,
-      }, this.forcePopupAlign)
-      if (props.treeCheckable && !val) {
-        this.setState({
-          sValue: this.getValue(props, [...this.sValue], false),
-        })
-      }
-      this.__emit('search', val)
-    },
+      // Selector Value List
+      if (valueRefresh) {
+        // Find out that value not exist in the tree
+        const missValueList = []
+        const filteredValueList = []
+        const keyList = []
 
-    onDropdownVisibleChange (open) {
-      // selection inside combobox cause click
-      if (!open && document.activeElement === this.getInputDOMNode()) {
-        // return;
-      }
-      this.setOpenState(open, undefined, !open)
-    },
-
-    // combobox ignore
-    onKeyDown (event) {
-      const props = this.$props
-      if (props.disabled) {
-        return
-      }
-      const keyCode = event.keyCode
-      if (this.sOpen && !this.getInputDOMNode()) {
-        this.onInputKeyDown(event)
-      } else if (keyCode === KeyCode.ENTER || keyCode === KeyCode.DOWN) {
-        this.setOpenState(true)
-        event.preventDefault()
-      }
-    },
-
-    onInputKeyDown (event) {
-      const props = this.$props
-      if (props.disabled) {
-        return
-      }
-      const state = this.$data
-      const keyCode = event.keyCode
-      if (isMultiple(props) && !event.target.value && keyCode === KeyCode.BACKSPACE) {
-        const value = state.sValue.concat()
-        if (value.length) {
-          const popValue = value.pop()
-          this.removeSelected(this.isLabelInValue() ? popValue : popValue.value)
+        // Get latest value list
+        let latestValueList = newState._valueList
+        if (!latestValueList) {
+          // Also need add prev missValueList to avoid new treeNodes contains the value
+          latestValueList = [...prevState._valueList, ...prevState._missValueList]
         }
-        return
-      }
-      if (keyCode === KeyCode.DOWN) {
-        if (!state.sOpen) {
-          this.openIfHasChildren()
-          event.preventDefault()
-          event.stopPropagation()
-          return
-        }
-      } else if (keyCode === KeyCode.ESC) {
-        if (state.sOpen) {
-          this.setOpenState(false)
-          event.preventDefault()
-          event.stopPropagation()
-        }
-        return
-      }
-    },
 
-    onSelect (selectedKeys, info) {
-      const item = info.node
-      let value = this.sValue
-      const props = this.$props
-      const selectedValue = getValuePropValue(item)
-      const selectedLabel = this.getLabelFromNode(item)
-      const checkableSelect = props.treeCheckable && info.event === 'select'
-      let event = selectedValue
-      if (this.isLabelInValue()) {
-        event = {
-          value: event,
-          label: selectedLabel,
-        }
-      }
-      if (info.selected === false) {
-        this.onDeselect(info)
-        if (!checkableSelect) return
-      }
-      this.__emit('select', event, item, info)
+        // Get key by value
+        latestValueList
+          .forEach((wrapperValue) => {
+            const { value } = wrapperValue
+            const entity = (newState._valueEntities || prevState._valueEntities)[value]
 
-      const checkEvt = info.event === 'check'
-      if (isMultiple(props)) {
-        this.$nextTick(() => { // clearSearchInput will change sInputValue
-          this.clearSearchInput()
-        })
-        if (checkEvt) {
-          value = this.getCheckedNodes(info, props).map(n => {
-            return {
-              value: getValuePropValue(n),
-              label: this.getLabelFromNode(n),
+            if (entity) {
+              keyList.push(entity.key)
+              filteredValueList.push(wrapperValue)
+              return
             }
+
+            // If not match, it may caused by ajax load. We need keep this
+            missValueList.push(wrapperValue)
+          })
+
+        // We need calculate the value when tree is checked tree
+        if (treeCheckable && !treeCheckStrictly) {
+          // Calculate the keys need to be checked
+          const { checkedKeys } = conductCheck(
+            keyList,
+            true,
+            newState._keyEntities || prevState._keyEntities,
+          )
+
+          // Format value list again for internal usage
+          newState._valueList = checkedKeys.map(key => ({
+            value: (newState._keyEntities || prevState._keyEntities)[key].value,
+          }))
+        } else {
+          newState._valueList = filteredValueList
+        }
+
+        // Fill the missValueList, we still need display in the selector
+        newState._missValueList = missValueList
+
+        // Calculate the value list for `Selector` usage
+        newState._selectorValueList = formatSelectorValue(
+          newState._valueList,
+          nextProps,
+          newState._valueEntities || prevState._valueEntities,
+        )
+      }
+
+      // [Legacy] To align with `Select` component,
+      // We use `searchValue` instead of `inputValue` but still keep the api
+      // `inputValue` support `null` to work as `autoClearSearchValue`
+      processState('inputValue', (propValue) => {
+        if (propValue !== null) {
+          newState._searchValue = propValue
+        }
+      })
+
+      // Search value
+      processState('searchValue', (propValue) => {
+        newState._searchValue = propValue
+      })
+
+      // Do the search logic
+      if (
+        newState._searchValue !== undefined ||
+        (prevState._searchValue && treeNodes)
+      ) {
+        const searchValue = newState._searchValue !== undefined ? newState._searchValue : prevState._searchValue
+        const upperSearchValue = String(searchValue).toUpperCase()
+
+        let filterTreeNodeFn = filterTreeNode
+        if (filterTreeNode === false) {
+          // Don't filter if is false
+          filterTreeNodeFn = () => true
+        } else if (typeof filterTreeNodeFn !== 'function') {
+          // When is not function (true or undefined), use inner filter
+          filterTreeNodeFn = (_, node) => {
+            const nodeValue = String(getPropsData(node)[treeNodeFilterProp]).toUpperCase()
+            return nodeValue.indexOf(upperSearchValue) !== -1
+          }
+        }
+
+        newState._filteredTreeNodes = getFilterTree(
+          this.$createElement,
+          newState._treeNodes || prevState._treeNodes,
+          searchValue,
+          filterTreeNodeFn,
+          newState._valueEntities || prevState._valueEntities,
+        )
+      }
+
+      // We should re-calculate the halfCheckedKeys when in search mode
+      if (
+        valueRefresh && treeCheckable && !treeCheckStrictly &&
+         (newState._searchValue || prevState._searchValue)
+      ) {
+        newState._searchHalfCheckedKeys = getHalfCheckedKeys(
+          newState._valueList,
+          newState._valueEntities || prevState._valueEntities,
+        )
+      }
+
+      // Checked Strategy
+      processState('showCheckedStrategy', () => {
+        newState._selectorValueList = newState._selectorValueList || formatSelectorValue(
+          newState._valueList || prevState._valueList,
+          nextProps,
+          newState._valueEntities || prevState._valueEntities,
+        )
+      })
+
+      return newState
+    },
+    // ==================== Selector ====================
+    onSelectorFocus () {
+      this.setState({ _focused: true })
+    },
+
+    onSelectorBlur () {
+      this.setState({ _focused: false })
+
+      // TODO: Close when Popup is also not focused
+      // this.setState({ open: false });
+    },
+
+    // Handle key board event in both Selector and Popup
+    onComponentKeyDown  (event) {
+      const { _open: open } = this.$data
+      const { keyCode } = event
+
+      if (!open) {
+        if ([KeyCode.ENTER, KeyCode.DOWN].indexOf(keyCode) !== -1) {
+          this.setOpenState(true)
+        }
+      } else if (KeyCode.ESC === keyCode) {
+        this.setOpenState(false)
+      } else if ([KeyCode.UP, KeyCode.DOWN, KeyCode.LEFT, KeyCode.RIGHT].indexOf(keyCode) !== -1) {
+        // TODO: Handle `open` state
+        event.stopPropagation()
+      }
+    },
+
+    onDeselect  (wrappedValue, node, nodeEventInfo) {
+      this.__emit('deselect', wrappedValue, node, nodeEventInfo)
+    },
+
+    onSelectorClear (event) {
+      const { disabled } = this.$props
+      if (disabled) return
+
+      this.triggerChange([], [])
+
+      if (!this.isSearchValueControlled()) {
+        this.setUncontrolledState({
+          _searchValue: '',
+          _filteredTreeNodes: null,
+        })
+      }
+
+      event.stopPropagation()
+    },
+
+    onMultipleSelectorRemove  (event, removeValue) {
+      event.stopPropagation()
+
+      const { _valueList: valueList, _missValueList: missValueList, _valueEntities: valueEntities } = this.$data
+
+      const { treeCheckable, treeCheckStrictly, treeNodeLabelProp, disabled } = this.$props
+      if (disabled) return
+
+      // Find trigger entity
+      const triggerEntity = valueEntities[removeValue]
+
+      // Clean up value
+      let newValueList = valueList
+      if (triggerEntity) {
+        // If value is in tree
+        if (treeCheckable && !treeCheckStrictly) {
+          newValueList = valueList.filter(({ value }) => {
+            const entity = valueEntities[value]
+            return !isPosRelated(entity.pos, triggerEntity.pos)
           })
         } else {
-          if (value.some(i => i.value === selectedValue)) {
-            return
-          }
-          value = value.concat([{
-            value: selectedValue,
-            label: selectedLabel,
-          }])
+          newValueList = valueList.filter(({ value }) => value !== removeValue)
         }
-      } else {
-        if (value.length && value[0].value === selectedValue) {
-          this.setOpenState(false)
-          return
-        }
-        value = [{
-          value: selectedValue,
-          label: selectedLabel,
-        }]
-        this.setOpenState(false)
       }
 
+      const triggerNode = triggerEntity ? triggerEntity.node : null
+
       const extraInfo = {
-        triggerValue: selectedValue,
-        triggerNode: item,
+        triggerValue: removeValue,
+        triggerNode,
       }
-      if (checkEvt) {
-        extraInfo.checked = info.checked
-        // if inputValue existing, tree is checkStrictly
-        extraInfo.allCheckedNodes = props.treeCheckStrictly || this.sInputValue
-          ? info.checkedNodes : flatToHierarchy(info.checkedNodesPositions)
-        this._checkedNodes = info.checkedNodesPositions
-        const _tree = this.getPopupComponentRefs()
-        this._treeNodesStates = _tree.checkKeys
-      } else {
-        extraInfo.selected = info.selected
+      const deselectInfo = {
+        node: triggerNode,
       }
 
-      this.fireChange(value, extraInfo)
-      if (props.inputValue === null) {
+      // [Legacy] Little hack on this to make same action as `onCheck` event.
+      if (treeCheckable) {
+        const filteredEntityList = newValueList.map(({ value }) => valueEntities[value])
+
+        deselectInfo.event = 'check'
+        deselectInfo.checked = false
+        deselectInfo.checkedNodes = filteredEntityList.map(({ node }) => node)
+        deselectInfo.checkedNodesPositions = filteredEntityList
+          .map(({ node, pos }) => ({ node, pos }))
+
+        if (treeCheckStrictly) {
+          extraInfo.allCheckedNodes = deselectInfo.checkedNodes
+        } else {
+          // TODO: It's too expansive to get `halfCheckedKeys` in onDeselect. Not pass this.
+          extraInfo.allCheckedNodes = flatToHierarchy(filteredEntityList)
+            .map(({ node }) => node)
+        }
+      } else {
+        deselectInfo.event = 'select'
+        deselectInfo.selected = false
+        deselectInfo.selectedNodes = newValueList.map(({ value }) => (valueEntities[value] || {}).node)
+      }
+
+      // Some value user pass prop is not in the tree, we also need clean it
+      const newMissValueList = missValueList.filter(({ value }) => value !== removeValue)
+      let wrappedValue
+      if (this.isLabelInValue()) {
+        wrappedValue = {
+          label: triggerNode ? getPropsData(triggerNode)[treeNodeLabelProp] : null,
+          value: removeValue,
+        }
+      } else {
+        wrappedValue = removeValue
+      }
+
+      this.onDeselect(wrappedValue, triggerNode, deselectInfo)
+
+      this.triggerChange(newMissValueList, newValueList, extraInfo)
+    },
+
+    // ===================== Popup ======================
+    onValueTrigger  (isAdd, nodeList, nodeEventInfo, nodeExtraInfo) {
+      const { node } = nodeEventInfo
+      const { value } = node.$props
+      const { _missValueList: missValueList, _valueEntities: valueEntities, _keyEntities: keyEntities, _searchValue: searchValue } = this.$data
+      const {
+        disabled, inputValue,
+        treeNodeLabelProp,
+        treeCheckable, treeCheckStrictly, autoClearSearchValue,
+      } = this.$props
+      const label = node.$props[treeNodeLabelProp]
+
+      if (disabled) return
+
+      // Wrap the return value for user
+      let wrappedValue
+      if (this.isLabelInValue()) {
+        wrappedValue = {
+          value,
+          label,
+        }
+      } else {
+        wrappedValue = value
+      }
+
+      // [Legacy] Origin code not trigger `onDeselect` every time. Let's align the behaviour.
+      if (isAdd) {
+        this.__emit('select', wrappedValue, node, nodeEventInfo)
+      } else {
+        this.__emit('deselect', wrappedValue, node, nodeEventInfo)
+      }
+
+      // Get wrapped value list.
+      // This is a bit hack cause we use key to match the value.
+      let newValueList = nodeList.map(node => {
+        const props = getPropsData(node)
+        return {
+          value: props.value,
+          label: props[treeNodeLabelProp],
+        }
+      })
+
+      // When is `treeCheckable` and with `searchValue`, `valueList` is not full filled.
+      // We need calculate the missing nodes.
+      if (treeCheckable && !treeCheckStrictly) {
+        let keyList = newValueList.map(({ value: val }) => valueEntities[val].key)
+        if (isAdd) {
+          keyList = conductCheck(
+            keyList,
+            true,
+            keyEntities,
+          ).checkedKeys
+        } else {
+          keyList = conductCheck(
+            [valueEntities[value].key],
+            false,
+            keyEntities,
+            { checkedKeys: keyList },
+          ).checkedKeys
+        }
+        newValueList = keyList.map(key => {
+          const props = getPropsData(keyEntities[key].node)
+          return {
+            value: props.value,
+            label: props[treeNodeLabelProp],
+          }
+        })
+      }
+
+      // Clean up `searchValue` when this prop is set
+      if (autoClearSearchValue || inputValue === null) {
+        // Clean state `searchValue` if uncontrolled
+        if (!this.isSearchValueControlled()) {
+          this.setUncontrolledState({
+            _searchValue: '',
+            _filteredTreeNodes: null,
+          })
+        }
+
+        // Trigger onSearch if `searchValue` to be empty.
+        // We should also trigger onSearch with empty string here
+        // since if user use `treeExpandedKeys`, it need user have the ability to reset it.
+        if (searchValue && searchValue.length) {
+          this.__emit('search', '')
+        }
+      }
+
+      // [Legacy] Provide extra info
+      const extraInfo = {
+        ...nodeExtraInfo,
+        triggerValue: value,
+        triggerNode: node,
+      }
+
+      this.triggerChange(missValueList, newValueList, extraInfo)
+    },
+
+    onTreeNodeSelect (_, nodeEventInfo) {
+      const { _valueList: valueList, _valueEntities: valueEntities } = this.$data
+      const { treeCheckable, multiple } = this.$props
+      if (treeCheckable) return
+
+      if (!multiple) {
+        this.setOpenState(false)
+      }
+
+      const isAdd = nodeEventInfo.selected
+      const { $props: { value: selectedValue }} = nodeEventInfo.node
+
+      let newValueList
+
+      if (!multiple) {
+        newValueList = [{ value: selectedValue }]
+      } else {
+        newValueList = valueList.filter(({ value }) => value !== selectedValue)
+        if (isAdd) {
+          newValueList.push({ value: selectedValue })
+        }
+      }
+
+      const selectedNodes = newValueList
+        .map(({ value }) => valueEntities[value])
+        .filter(entity => entity)
+        .map(({ node }) => node)
+
+      this.onValueTrigger(isAdd, selectedNodes, nodeEventInfo, { selected: isAdd })
+    },
+
+    onTreeNodeCheck  (_, nodeEventInfo) {
+      const { _searchValue: searchValue, _keyEntities: keyEntities, _valueEntities: valueEntities, _valueList: valueList } = this.$data
+      const { treeCheckStrictly } = this.$props
+
+      const { checkedNodes, checkedNodesPositions } = nodeEventInfo
+      const isAdd = nodeEventInfo.checked
+
+      const extraInfo = {
+        checked: isAdd,
+      }
+
+      let checkedNodeList = checkedNodes
+
+      // [Legacy] Check event provide `allCheckedNodes`.
+      // When `treeCheckStrictly` or internal `searchValue` is set, TreeNode will be unrelated:
+      // - Related: Show the top checked nodes and has children prop.
+      // - Unrelated: Show all the checked nodes.
+      if (searchValue) {
+        const oriKeyList = valueList
+          .map(({ value }) => valueEntities[value])
+          .filter(entity => entity)
+          .map(({ key }) => key)
+
+        let keyList
+        if (isAdd) {
+          keyList = Array.from(
+            new Set([
+              ...oriKeyList,
+              ...checkedNodeList.map(node => {
+                const { value } = getPropsData(node)
+                return valueEntities[value].key
+              }),
+            ]),
+          )
+        } else {
+          keyList = conductCheck(
+            [getPropsData(nodeEventInfo.node).eventKey],
+            false,
+            keyEntities,
+            { checkedKeys: oriKeyList },
+          ).checkedKeys
+        }
+
+        checkedNodeList = keyList.map(key => keyEntities[key].node)
+
+        // Let's follow as not `treeCheckStrictly` format
+        extraInfo.allCheckedNodes = keyList.map(key => cleanEntity(keyEntities[key]))
+      } else if (treeCheckStrictly) {
+        extraInfo.allCheckedNodes = nodeEventInfo.checkedNodes
+      } else {
+        extraInfo.allCheckedNodes = flatToHierarchy(checkedNodesPositions)
+      }
+
+      this.onValueTrigger(isAdd, checkedNodeList, nodeEventInfo, extraInfo)
+    },
+
+    // ==================== Trigger =====================
+
+    onDropdownVisibleChange  (open) {
+      this.setOpenState(open, true)
+    },
+
+    onSearchInputChange  ({ target: { value }}) {
+      const { _treeNodes: treeNodes, _valueEntities: valueEntities } = this.$data
+      const { filterTreeNode, treeNodeFilterProp } = this.$props
+      this.__emit('search', value)
+
+      let isSet = false
+
+      if (!this.isSearchValueControlled()) {
+        isSet = this.setUncontrolledState({
+          _searchValue: value,
+        })
+        this.setOpenState(true)
+      }
+
+      if (isSet) {
+        // Do the search logic
+        const upperSearchValue = String(value).toUpperCase()
+
+        let filterTreeNodeFn = filterTreeNode
+        if (!filterTreeNodeFn) {
+          filterTreeNodeFn = (_, node) => {
+            const nodeValue = String(getPropsData(node)[treeNodeFilterProp]).toUpperCase()
+            return nodeValue.indexOf(upperSearchValue) !== -1
+          }
+        }
+
         this.setState({
-          sInputValue: '',
+          _filteredTreeNodes: getFilterTree(this.$createElement, treeNodes, value, filterTreeNodeFn, valueEntities),
         })
       }
     },
 
-    onDeselect (info) {
-      this.removeSelected(getValuePropValue(info.node))
-      if (!isMultiple(this.$props)) {
-        this.setOpenState(false)
-      } else {
-        this.clearSearchInput()
-      }
-    },
+    onSearchInputKeyDown  (event) {
+      const { _searchValue: searchValue, _valueList: valueList } = this.$data
 
-    onPlaceholderClick () {
-      this.getInputDOMNode().focus()
-    },
+      const { keyCode } = event
 
-    onClearSelection (event) {
-      const props = this.$props
-      const state = this.$data
-      if (props.disabled) {
-        return
-      }
-      event.stopPropagation()
-      this._cacheTreeNodesStates = 'no'
-      this._checkedNodes = []
-      if (state.sInputValue || state.sValue.length) {
-        this.setOpenState(false)
-        if (typeof props.inputValue === 'undefined') {
-          this.setState({
-            sInputValue: '',
-          }, () => {
-            this.fireChange([])
-          })
-        } else {
-          this.fireChange([])
-        }
+      if (
+        KeyCode.BACKSPACE === keyCode &&
+        this.isMultiple() &&
+        !searchValue &&
+        valueList.length
+      ) {
+        const lastValue = valueList[valueList.length - 1].value
+        this.onMultipleSelectorRemove(event, lastValue)
       }
     },
 
@@ -400,642 +802,250 @@ const Select = {
       this.forcePopupAlign()
     },
 
-    getLabelFromNode (child) {
-      return getPropValue(child, this.$props.treeNodeLabelProp)
-    },
-
-    getLabelFromProps (props, value) {
-      if (value === undefined) {
-        return null
-      }
-      let label = null
-      loopAllChildren(this.renderedTreeData || props.children, item => {
-        if (getValuePropValue(item) === value) {
-          label = this.getLabelFromNode(item)
-        }
-      })
-      if (label === null) {
-        return value
-      }
-      return label
-    },
-
-    getDropdownContainer () {
-      if (!this.dropdownContainer) {
-        this.dropdownContainer = document.createElement('div')
-        document.body.appendChild(this.dropdownContainer)
-      }
-      return this.dropdownContainer
-    },
-
-    getSearchPlaceholderElement (hidden) {
-      const props = this.$props
-      let placeholder
-      if (isMultiple(props)) {
-        placeholder = getComponentFromProp(this, 'placeholder') || getComponentFromProp(this, 'searchPlaceholder')
-      } else {
-        placeholder = getComponentFromProp(this, 'searchPlaceholder')
-      }
-      if (placeholder) {
-        return (
-          <span
-            style={{ display: hidden ? 'none' : 'block' }}
-            onClick={this.onPlaceholderClick}
-            class={`${props.prefixCls}-search__field__placeholder`}
-          >
-            {placeholder}
-          </span>
-        )
-      }
-      return null
-    },
-
-    getInputElement () {
-      const { sInputValue } = this.$data
-      const { prefixCls, disabled } = this.$props
-      const multiple = isMultiple(this.$props)
-      const inputListeners = {
-        input: this.onInputChange,
-        keydown: this.onInputKeyDown,
-      }
-      if (multiple) {
-        inputListeners.blur = this.onBlur
-        inputListeners.focus = this.onFocus
-      }
-      return (
-        <span class={`${prefixCls}-search__field__wrap`}>
-          <input
-            ref='inputInstance'
-            {...{ on: inputListeners }}
-            value={sInputValue}
-            disabled={disabled}
-            class={`${prefixCls}-search__field`}
-            role='textbox'
-          />
-          <span
-            ref='inputMirrorInstance'
-            class={`${prefixCls}-search__field__mirror`}
-          >
-            {sInputValue}&nbsp;
-          </span>
-          {isMultiple(this.$props) ? null : this.getSearchPlaceholderElement(!!sInputValue)}
-        </span>
-      )
-    },
-
-    getInputDOMNode () {
-      return this.$refs.inputInstance
-    },
-
-    getPopupDOMNode () {
-      return this.$refs.trigger.getPopupDOMNode()
-    },
-
-    getPopupComponentRefs () {
-      return this.$refs.trigger.getPopupEleRefs()
-    },
-
-    getValue (_props, val, init = true) {
-      let value = val
-      // if inputValue existing, tree is checkStrictly
-      const _strict = init === '__strict' ||
-        init && (this.sInputValue ||
-        this.inputValue !== _props.inputValue)
-      if (_props.treeCheckable &&
-        (_props.treeCheckStrictly || _strict)) {
-        this.halfCheckedValues = []
-        value = []
-        val.forEach(i => {
-          if (!i.halfChecked) {
-            value.push(i)
-          } else {
-            this.halfCheckedValues.push(i)
-          }
-        })
-      }
-      // if (!(_props.treeCheckable && !_props.treeCheckStrictly)) {
-      if (!_props.treeCheckable || _props.treeCheckable &&
-        (_props.treeCheckStrictly || _strict)) {
-        return value
-      }
-      let checkedTreeNodes
-      if (this._cachetreeData && this._cacheTreeNodesStates && this._checkedNodes &&
-         !this.sInputValue) {
-        this.checkedTreeNodes = checkedTreeNodes = this._checkedNodes
-      } else {
-        /**
-         * Note: `this._treeNodesStates`'s treeNodesStates must correspond to nodes of the
-         * final tree (`processTreeNode` function from SelectTrigger.jsx produce the final tree).
-         *
-         * And, `this._treeNodesStates` from `onSelect` is previous value,
-         * so it perhaps only have a few nodes, but the newly filtered tree can have many nodes,
-         * thus, you cannot use previous _treeNodesStates.
-         */
-        // getTreeNodesStates is not effective.
-        this._treeNodesStates = getTreeNodesStates(
-          this.renderedTreeData || _props.children,
-          value.map(item => item.value)
-        )
-        this.checkedTreeNodes = checkedTreeNodes = this._treeNodesStates.checkedNodes
-      }
-      const mapLabVal = arr => arr.map(itemObj => {
-        return {
-          value: getValuePropValue(itemObj.node),
-          label: getPropValue(itemObj.node, _props.treeNodeLabelProp),
-        }
-      })
-      const props = this.$props
-      let checkedValues = []
-      if (props.showCheckedStrategy === SHOW_ALL) {
-        checkedValues = mapLabVal(checkedTreeNodes)
-      } else if (props.showCheckedStrategy === SHOW_PARENT) {
-        const posArr = filterParentPosition(checkedTreeNodes.map(itemObj => itemObj.pos))
-        checkedValues = mapLabVal(checkedTreeNodes.filter(
-          itemObj => posArr.indexOf(itemObj.pos) !== -1
-        ))
-      } else {
-        checkedValues = mapLabVal(checkedTreeNodes.filter(itemObj => {
-          return !itemObj.node.componentOptions.children
-        }))
-      }
-      return checkedValues
-    },
-
-    getCheckedNodes (info, props) {
-      // TODO treeCheckable does not support tags/dynamic
-      let { checkedNodes } = info
-      // if inputValue existing, tree is checkStrictly
-      if (props.treeCheckStrictly || this.sInputValue) {
-        return checkedNodes
-      }
-      const checkedNodesPositions = info.checkedNodesPositions
-      if (props.showCheckedStrategy === SHOW_ALL) {
-        // checkedNodes = checkedNodes
-      } else if (props.showCheckedStrategy === SHOW_PARENT) {
-        const posArr = filterParentPosition(checkedNodesPositions.map(itemObj => itemObj.pos))
-        checkedNodes = checkedNodesPositions.filter(itemObj => posArr.indexOf(itemObj.pos) !== -1)
-          .map(itemObj => itemObj.node)
-      } else {
-        checkedNodes = checkedNodes.filter(n => {
-          return !n.componentOptions.children
-        })
-      }
-      return checkedNodes
-    },
-
-    getDeselectedValue (selectedValue) {
-      const checkedTreeNodes = this.checkedTreeNodes
-      let unCheckPos
-      checkedTreeNodes.forEach(itemObj => {
-        const nodeProps = getAllProps(itemObj.node)
-        if (nodeProps.value === selectedValue) {
-          unCheckPos = itemObj.pos
-        }
-      })
-      const newVals = []
-      const newCkTns = []
-      checkedTreeNodes.forEach(itemObj => {
-        if (isPositionPrefix(itemObj.pos, unCheckPos) || isPositionPrefix(unCheckPos, itemObj.pos)) {
-          // Filter ancestral and children nodes when uncheck a node.
-          return
-        }
-        const nodeProps = getAllProps(itemObj.node)
-        newCkTns.push(itemObj)
-        newVals.push(nodeProps.value)
-      })
-      this.checkedTreeNodes = this._checkedNodes = newCkTns
-      const nv = this.sValue.filter(val => newVals.indexOf(val.value) !== -1)
-      this.fireChange(nv, { triggerValue: selectedValue, clear: true })
-    },
-
-    setOpenState (open, needFocus, documentClickClose = false) {
-      this.clearDelayTimer()
-      const { $props: props } = this
-      // can not optimize, if children is empty
-      // if (this.sOpen === open) {
-      //   return;
-      // }
-      if (!this.$props.dropdownVisibleChange(open, { documentClickClose })) {
-        return
-      }
-      this.setState({
-        sOpen: open,
-      }, () => {
-        if (needFocus || open) {
-          // Input dom init after first time component render
-          // Add delay for this to get focus
-          setTimeout(() => {
-            if (open || isMultiple(props)) {
-              const input = this.getInputDOMNode()
-              if (input && document.activeElement !== input) {
-                input.focus()
-              }
-            } else if (this.$refs.selection) {
-              this.$refs.selection.focus()
-            }
-          }, 0)
-        }
-      })
-    },
-
-    clearSearchInput () {
-      this.getInputDOMNode().focus()
-      if (!hasProp(this, 'inputValue')) {
-        this.setState({ sInputValue: '' })
-      }
-    },
-
-    addLabelToValue (props, value_) {
-      let value = value_
-      if (this.isLabelInValue()) {
-        value.forEach((v, i) => {
-          if (Object.prototype.toString.call(value[i]) !== '[object Object]') {
-            value[i] = {
-              value: '',
-              label: '',
-            }
-            return
-          }
-          v.label = v.label || this.getLabelFromProps(props, v.value)
-        })
-      } else {
-        value = value.map(v => {
-          return {
-            value: v,
-            label: this.getLabelFromProps(props, v),
-          }
-        })
-      }
-      return value
-    },
-
-    clearDelayTimer () {
-      if (this.delayTimer) {
-        clearTimeout(this.delayTimer)
-        this.delayTimer = null
-      }
-    },
-
-    removeSelected (selectedVal, e) {
-      const props = this.$props
-      if (props.disabled) {
-        return
-      }
-
-      // Do not trigger Trigger popup
-      if (e && e.stopPropagation) {
-        e.stopPropagation()
-      }
-
-      this._cacheTreeNodesStates = 'no'
-      if (props.treeCheckable &&
-        (props.showCheckedStrategy === SHOW_ALL || props.showCheckedStrategy === SHOW_PARENT) &&
-        !(props.treeCheckStrictly || this.sInputValue)) {
-        this.getDeselectedValue(selectedVal)
-        return
-      }
-      // click the node's `x`(in select box), likely trigger the TreeNode's `unCheck` event,
-      // cautiously, they are completely different, think about it, the tree may not render at first,
-      // but the nodes in select box are ready.
-      let label
-      const value = this.sValue.filter((singleValue) => {
-        if (singleValue.value === selectedVal) {
-          label = singleValue.label
-        }
-        return (singleValue.value !== selectedVal)
-      })
-      const canMultiple = isMultiple(props)
-
-      if (canMultiple) {
-        let event = selectedVal
-        if (this.isLabelInValue()) {
-          event = {
-            value: selectedVal,
-            label,
-          }
-        }
-        this.__emit('deselect', event)
-      }
-      if (props.treeCheckable) {
-        if (this.checkedTreeNodes && this.checkedTreeNodes.length) {
-          this.checkedTreeNodes = this._checkedNodes = this.checkedTreeNodes.filter(item => {
-            const nodeProps = getAllProps(item.node)
-            return value.some(i => i.value === nodeProps.value)
-          })
-        }
-      }
-
-      this.fireChange(value, { triggerValue: selectedVal, clear: true })
-    },
-
-    openIfHasChildren () {
-      const props = this.$props
-      if (props.children.length || (props.treeData && props.treeData.length) || !isMultiple(props)) {
-        this.setOpenState(true)
-      }
-    },
-
-    fireChange (value, extraInfo = {}) {
+    /**
+ * Only update the value which is not in props
+ */
+    setUncontrolledState (state) {
+      let needSync = false
+      const newState = {}
       const props = getOptionProps(this)
-      const vals = value.map(i => i.value)
-      const sv = this.sValue.map(i => i.value)
-      if (vals.length !== sv.length || !vals.every((val, index) => sv[index] === val)) {
-        const ex = {
-          preValue: [...this.sValue],
-          ...extraInfo,
-        }
-        let labs = null
-        let vls = value
-        if (!this.isLabelInValue()) {
-          labs = value.map(i => i.label)
-          vls = vls.map(v => v.value)
-        } else if (this.halfCheckedValues && this.halfCheckedValues.length) {
-          this.halfCheckedValues.forEach(i => {
-            if (!vls.some(v => v.value === i.value)) {
-              vls.push(i)
-            }
-          })
-        }
-        if (props.treeCheckable && ex.clear) {
-          const treeData = this.renderedTreeData || props.children
-          ex.allCheckedNodes = flatToHierarchy(filterAllCheckedData(vals, treeData))
-        }
-        if (props.treeCheckable && this.sInputValue) {
-          const _vls = [...this.sValue]
-          if (ex.checked) {
-            value.forEach(i => {
-              if (_vls.every(ii => ii.value !== i.value)) {
-                _vls.push({ ...i })
-              }
-            })
-          } else {
-            let index
-            const includeVal = _vls.some((i, ind) => {
-              if (i.value === ex.triggerValue) {
-                index = ind
-                return true
-              }
-            })
-            if (includeVal) {
-              _vls.splice(index, 1)
-            }
-          }
-          vls = _vls
-          if (!this.isLabelInValue()) {
-            labs = _vls.map(v => v.label)
-            vls = _vls.map(v => v.value)
-          }
-        }
-        this._savedValue = isMultiple(props) ? vls : vls[0]
-        this.__emit('change', this._savedValue, labs, ex)
-        if (!('value' in props)) {
-          this._cacheTreeNodesStates = false
-          this.setState({
-            sValue: this.getValue(props, toArray(this._savedValue).map((v, i) => {
-              return this.isLabelInValue() ? v : {
-                value: v,
-                label: labs && labs[i],
-              }
-            })),
-          }, this.forcePopupAlign)
-        }
+      Object.keys(state).forEach(name => {
+        if (name.slice(1) in props) return
+
+        needSync = true
+        newState[name] = state[name]
+      })
+
+      if (needSync) {
+        this.setState(newState)
       }
+
+      return needSync
     },
 
-    isLabelInValue () {
-      const { treeCheckable, treeCheckStrictly, labelInValue } = this.$props
-      if (treeCheckable && treeCheckStrictly) {
-        return true
+    // [Legacy] Origin provide `documentClickClose` which triggered by `Trigger`
+    // Currently `TreeSelect` align the hide popup logic as `Select` which blur to hide.
+    // `documentClickClose` is not accurate anymore. Let's just keep the key word.
+    setOpenState  (open, byTrigger = false) {
+      const { dropdownVisibleChange } = this.$props
+
+      if (
+        dropdownVisibleChange &&
+        dropdownVisibleChange(open, { documentClickClose: !open && byTrigger }) === false
+      ) {
+        return
       }
-      return labelInValue || false
-    },
-    onFocus (e) {
-      this.__emit('focus', e)
-    },
-    onBlur (e) {
-      this.__emit('blur', e)
+
+      this.setUncontrolledState({ _open: open })
     },
 
-    focus () {
-      if (!isMultiple(this.$props)) {
-        this.$refs.selection.focus()
-      } else {
-        this.getInputDOMNode().focus()
-      }
+    // Tree checkable is also a multiple case
+    isMultiple  () {
+      const { multiple, treeCheckable } = this.$props
+      return !!(multiple || treeCheckable)
     },
 
-    blur () {
-      if (!isMultiple(this.$props)) {
-        this.$refs.selection.blur()
-      } else {
-        this.getInputDOMNode().blur()
-      }
+    isLabelInValue  () {
+      return isLabelInValue(this.$props)
+    },
+
+    // [Legacy] To align with `Select` component,
+    // We use `searchValue` instead of `inputValue`
+    // but currently still need support that.
+    // Add this method the check if is controlled
+    isSearchValueControlled () {
+      const props = getOptionProps(this)
+      const { inputValue } = props
+      if ('searchValue' in props) return true
+      return ('inputValue' in props) && inputValue !== null
     },
 
     forcePopupAlign () {
-      this.$refs.trigger.$refs.trigger.forcePopupAlign()
+      const $trigger = this.selectTriggerRef.current
+      if ($trigger) {
+        $trigger.forcePopupAlign()
+      }
     },
 
-    renderTopControlNode () {
-      const { sValue: value } = this.$data
-      const props = this.$props
-      const { choiceTransitionName, prefixCls, maxTagTextLength, removeIcon } = props
-      const multiple = isMultiple(props)
-
-      // single and not combobox, input is inside dropdown
-      if (!multiple) {
-        let innerNode = (<span
-          key='placeholder'
-          class={`${prefixCls}-selection__placeholder`}
-        >
-          {getComponentFromProp(this, 'placeholder') || ''}
-        </span>)
-        if (value.length) {
-          innerNode = (<span
-            key='value'
-            title={toTitle(value[0].label)}
-            class={`${prefixCls}-selection-selected-value`}
-          >
-            {value[0].label}
-          </span>)
-        }
-        return (<span class={`${prefixCls}-selection__rendered`}>
-          {innerNode}
-        </span>)
-      }
-
-      const selectedValueNodes = value.map((singleValue) => {
-        let content = singleValue.label
-        const title = content
-        if (maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength) {
-          content = `${content.slice(0, maxTagTextLength)}...`
-        }
-        return (
-          <li
-            style={UNSELECTABLE_STYLE}
-            onMousedown={preventDefaultEvent}
-            class={`${prefixCls}-selection__choice`}
-            key={singleValue.value}
-            title={toTitle(title)}
-            {...{ attrs: UNSELECTABLE_ATTRIBUTE }}
-          >
-            <span
-              class={`${prefixCls}-selection__choice__remove`}
-              onClick={(event) => {
-                this.removeSelected(singleValue.value, event)
-              }}
-            >{removeIcon}</span>
-            <span class={`${prefixCls}-selection__choice__content`}>{content}</span>
-          </li>
-        )
+    delayForcePopupAlign  () {
+      // Wait 2 frame to avoid dom update & dom algin in the same time
+      // https://github.com/ant-design/ant-design/issues/12031
+      raf(() => {
+        raf(this.forcePopupAlign)
       })
-
-      selectedValueNodes.push(<li
-        class={`${prefixCls}-search ${prefixCls}-search--inline`}
-        key='__input'
-      >
-        {this.getInputElement()}
-      </li>)
-      const className = `${prefixCls}-selection__rendered`
-      if (choiceTransitionName) {
-        const transitionProps = getTransitionProps(choiceTransitionName, {
-          tag: 'ul',
-          afterLeave: this.onChoiceAnimationLeave,
-        })
-        return (<transition-group
-          class={className}
-          {...transitionProps}
-        >
-          {selectedValueNodes}
-        </transition-group>)
-      }
-      return (<ul class={className}>{selectedValueNodes}</ul>)
     },
 
-    renderTreeData (props) {
-      const validProps = props || this.$props
-      if (validProps.treeData) {
-        if (props && props.treeData === this.preProps.treeData && this.renderedTreeData) {
-          // cache and use pre data.
-          this._cachetreeData = true
-          return this.renderedTreeData
-        }
-        this._cachetreeData = false
-        let treeData = [...validProps.treeData]
-        // process treeDataSimpleMode
-        if (validProps.treeDataSimpleMode) {
-          let simpleFormat = {
-            id: 'id',
-            pId: 'pId',
-            rootPId: null,
-          }
-          if (Object.prototype.toString.call(validProps.treeDataSimpleMode) === '[object Object]') {
-            simpleFormat = { ...simpleFormat, ...validProps.treeDataSimpleMode }
-          }
-          treeData = processSimpleTreeData(treeData, simpleFormat)
-        }
-        return this.loopTreeData(treeData, undefined, this.preProps.treeCheckable)
+    /**
+ * 1. Update state valueList.
+ * 2. Fire `onChange` event to user.
+ */
+    triggerChange  (missValueList, valueList, extraInfo = {}) {
+      const { _valueEntities: valueEntities, _searchValue: searchValue } = this.$data
+      const props = getOptionProps(this)
+      const { disabled, treeCheckable, treeCheckStrictly } = props
+      if (disabled) return
+
+      // Trigger
+      const extra = {
+        // [Legacy] Always return as array contains label & value
+        preValue: this.$data._selectorValueList.map(({ label, value }) => ({ label, value })),
+        ...extraInfo,
       }
+
+      // Format value by `treeCheckStrictly`
+      const selectorValueList = formatSelectorValue(valueList, props, valueEntities)
+
+      if (!('value' in props)) {
+        const newState = {
+          _missValueList: missValueList,
+          _valueList: valueList,
+          _selectorValueList: selectorValueList,
+        }
+
+        if (searchValue && treeCheckable && !treeCheckStrictly) {
+          newState._searchHalfCheckedKeys = getHalfCheckedKeys(
+            valueList,
+            valueEntities,
+          )
+        }
+
+        this.setState(newState)
+      }
+
+      // Only do the logic when `onChange` function provided
+      if (this.$listeners.change) {
+        let connectValueList
+
+        // Get value by mode
+        if (this.isMultiple()) {
+          connectValueList = [...missValueList, ...selectorValueList]
+        } else {
+          connectValueList = selectorValueList.slice(0, 1)
+        }
+
+        let labelList = null
+        let returnValue
+
+        if (this.isLabelInValue()) {
+          returnValue = connectValueList.map(({ label, value }) => ({ label, value }))
+        } else {
+          labelList = []
+          returnValue = connectValueList.map(({ label, value }) => {
+            labelList.push(label)
+            return value
+          })
+        }
+
+        if (!this.isMultiple()) {
+          returnValue = returnValue[0]
+        }
+        this.__emit('change', returnValue, labelList, extra)
+      }
+    },
+
+    focus () {
+      this.selectorRef.current.focus()
+    },
+
+    blur () {
+      this.selectorRef.current.blur()
     },
   },
 
+  // ===================== Render =====================
+
   render () {
-    const props = this.$props
-    const multiple = isMultiple(props)
-    const state = this.$data
-    const { disabled, allowClear, prefixCls, inputIcon, clearIcon } = props
-    const ctrlNode = this.renderTopControlNode()
-    let extraSelectionProps = {}
-    if (!multiple) {
-      extraSelectionProps = {
-        on: {
-          keydown: this.onKeyDown,
-          blur: this.onBlur,
-          focus: this.onFocus,
-        },
-        attrs: {
-          tabIndex: 0,
-        },
-      }
-    }
-    const rootCls = {
-      [prefixCls]: 1,
-      [`${prefixCls}-open`]: state.sOpen,
-      [`${prefixCls}-focused`]: state.sOpen || state.sFocused,
-      // [`${prefixCls}-combobox`]: isCombobox(props),
-      [`${prefixCls}-disabled`]: disabled,
-      [`${prefixCls}-enabled`]: !disabled,
-      [`${prefixCls}-allow-clear`]: !!props.allowClear,
-    }
-    const clear = (<span
-      key='clear'
-      class={`${prefixCls}-selection__clear`}
-      onClick={this.onClearSelection}
-    >{clearIcon}</span>)
-    const selectTriggerProps = {
+    const {
+      _valueList: valueList, _missValueList: missValueList, _selectorValueList: selectorValueList,
+      _searchHalfCheckedKeys: searchHalfCheckedKeys,
+      _valueEntities: valueEntities, _keyEntities: keyEntities,
+      _searchValue: searchValue,
+      _open: open, _focused: focused,
+      _treeNodes: treeNodes, _filteredTreeNodes: filteredTreeNodes,
+    } = this.$data
+    const props = getOptionProps(this)
+    const { prefixCls, treeExpandedKeys } = props
+    const isMultiple = this.isMultiple()
+
+    const passProps = {
       props: {
         ...props,
-        treeNodes: props.children,
-        treeData: this.renderedTreeData,
-        _cachetreeData: this._cachetreeData,
-        _treeNodesStates: this._treeNodesStates,
-        halfCheckedValues: this.halfCheckedValues,
-        multiple: multiple,
-        disabled: disabled,
-        visible: state.sOpen,
-        inputValue: state.sInputValue,
-        inputElement: this.getInputElement(),
-        value: state.sValue,
-        dropdownVisibleChange: this.onDropdownVisibleChange,
-        getPopupContainer: props.getPopupContainer,
-        filterTreeNode: this.filterTreeNode === undefined ? filterFn : this.filterTreeNode,
+        isMultiple,
+        valueList,
+        searchHalfCheckedKeys,
+        selectorValueList: [...missValueList, ...selectorValueList],
+        valueEntities,
+        keyEntities,
+        searchValue,
+        upperSearchValue: (searchValue || '').toUpperCase(), // Perf save
+        open,
+        focused,
+        dropdownPrefixCls: `${prefixCls}-dropdown`,
+        ariaId: this.ariaId,
       },
       on: {
         ...this.$listeners,
-        select: this.onSelect,
+        choiceAnimationLeave: this.onChoiceAnimationLeave,
       },
-      ref: 'trigger',
+      scopedSlots: this.$scopedSlots,
     }
+    const popupProps = mergeProps(passProps, {
+      props: {
+        treeNodes,
+        filteredTreeNodes,
+        // Tree expanded control
+        treeExpandedKeys,
+        __propsSymbol__: Symbol(),
+      },
+      on: {
+        treeExpanded: this.delayForcePopupAlign,
+      },
+    })
+
+    const Popup = isMultiple ? MultiplePopup : SinglePopup
+    const $popup = (
+      <Popup
+        {...popupProps}
+      />
+    )
+
+    const Selector = isMultiple ? MultipleSelector : SingleSelector
+    const $selector = (
+      <Selector
+        {...passProps}
+        {...{
+          directives: [{
+            name: 'ant-ref',
+            value: this.selectorRef,
+          }] }}
+      />
+    )
+    const selectTriggerProps = mergeProps(passProps, {
+      props: {
+        popupElement: $popup,
+        dropdownVisibleChange: this.onDropdownVisibleChange,
+      },
+      directives: [{
+        name: 'ant-ref',
+        value: this.selectTriggerRef,
+      }],
+    })
     return (
-      <SelectTrigger {...selectTriggerProps}>
-        <span
-          onClick={props.onClick}
-          class={classnames(rootCls)}
-        >
-          <span
-            ref='selection'
-            key='selection'
-            class={`${prefixCls}-selection
-            ${prefixCls}-selection--${multiple ? 'multiple' : 'single'}`}
-            role='combobox'
-            aria-autocomplete='list'
-            aria-haspopup='true'
-            aria-expanded={state.sOpen}
-            {...extraSelectionProps}
-          >
-            {ctrlNode}
-            {allowClear && state.sValue.length &&
-          state.sValue[0].value ? clear : null}
-            {multiple || !props.showArrow ? null
-              : (<span
-                key='arrow'
-                class={`${prefixCls}-arrow`}
-                style={{ outline: 'none' }}
-              >
-                {inputIcon}
-              </span>)}
-            {multiple
-              ? this.getSearchPlaceholderElement(!!state.sInputValue || state.sValue.length)
-              : null}
-          </span>
-        </span>
+      <SelectTrigger
+        {...selectTriggerProps}
+      >
+        {$selector}
       </SelectTrigger>
     )
   },
 }
 
+Select.TreeNode = SelectNode
 Select.SHOW_ALL = SHOW_ALL
 Select.SHOW_PARENT = SHOW_PARENT
 Select.SHOW_CHILD = SHOW_CHILD
 
+// Let warning show correct component name
+Select.name = 'TreeSelect'
+
 export default Select
diff --git a/components/vc-tree-select/src/SelectNode.jsx b/components/vc-tree-select/src/SelectNode.jsx
new file mode 100644
index 000000000..7bfbdd52c
--- /dev/null
+++ b/components/vc-tree-select/src/SelectNode.jsx
@@ -0,0 +1,28 @@
+import { TreeNode } from '../../vc-tree'
+/**
+ * SelectNode wrapped the tree node.
+ * Let's use SelectNode instead of TreeNode
+ * since TreeNode is so confuse here.
+ */
+export default {
+  functional: true,
+  name: 'SelectNode',
+  isTreeNode: true,
+  props: TreeNode.props,
+  render (h, context) {
+    const { props, slots, listeners, data } = context
+    const $slots = slots()
+    const children = $slots.default
+    delete $slots.default
+    const treeNodeProps = {
+      ...data, on: { ...listeners, ...data.nativeOn }, props,
+    }
+    const slotsKey = Object.keys($slots)
+    return <TreeNode {...treeNodeProps}>
+      {children}
+      {slotsKey.length ? slotsKey.map(name => {
+        return <template slot={name}>{$slots[name]}</template>
+      }) : null}
+    </TreeNode>
+  },
+}
diff --git a/components/vc-tree-select/src/SelectTrigger.jsx b/components/vc-tree-select/src/SelectTrigger.jsx
index b3fdef475..8b8fe023d 100644
--- a/components/vc-tree-select/src/SelectTrigger.jsx
+++ b/components/vc-tree-select/src/SelectTrigger.jsx
@@ -1,18 +1,8 @@
 import PropTypes from '../../_util/vue-types'
-import classnames from 'classnames'
-import Trigger from '../../vc-trigger'
-import Tree, { TreeNode } from '../../vc-tree'
-import { SelectPropTypes } from './PropTypes'
-import BaseMixin from '../../_util/BaseMixin'
-import {
-  loopAllChildren,
-  flatToHierarchy,
-  getValuePropValue,
-  labelCompatible,
-} from './util'
 
-import { cloneElement } from '../../_util/vnode'
-import { getSlotOptions, getKey, getAllProps, getComponentFromProp } from '../../_util/props-util'
+import Trigger from '../../vc-trigger'
+import { createRef } from './util'
+import classNames from 'classnames'
 
 const BUILT_IN_PLACEMENTS = {
   bottomLeft: {
@@ -22,6 +12,7 @@ const BUILT_IN_PLACEMENTS = {
       adjustX: 0,
       adjustY: 1,
     },
+    ignoreShake: true,
   },
   topLeft: {
     points: ['bl', 'tl'],
@@ -30,335 +21,94 @@ const BUILT_IN_PLACEMENTS = {
       adjustX: 0,
       adjustY: 1,
     },
+    ignoreShake: true,
   },
 }
 
 const SelectTrigger = {
-  mixins: [BaseMixin],
   name: 'SelectTrigger',
   props: {
-    ...SelectPropTypes,
-    dropdownMatchSelectWidth: PropTypes.bool,
-    dropdownPopupAlign: PropTypes.object,
-    visible: PropTypes.bool,
-    filterTreeNode: PropTypes.any,
-    treeNodes: PropTypes.any,
-    inputValue: PropTypes.string,
+    // Pass by outside user props
+    disabled: PropTypes.bool,
+    showSearch: PropTypes.bool,
     prefixCls: PropTypes.string,
-    popupClassName: PropTypes.string,
-    _cachetreeData: PropTypes.any,
-    _treeNodesStates: PropTypes.any,
-    halfCheckedValues: PropTypes.any,
-    inputElement: PropTypes.any,
-  },
-  data () {
-    return {
-      sExpandedKeys: [],
-      fireOnExpand: false,
-      dropdownWidth: null,
-    }
-  },
+    dropdownPopupAlign: PropTypes.object,
+    dropdownClassName: PropTypes.string,
+    dropdownStyle: PropTypes.object,
+    transitionName: PropTypes.string,
+    animation: PropTypes.string,
+    getPopupContainer: PropTypes.func,
 
-  mounted () {
-    this.$nextTick(() => {
-      this.setDropdownWidth()
-    })
-  },
-  watch: {
-    inputValue (val) {
-      // set autoExpandParent to true
-      this.setState({
-        sExpandedKeys: [],
-        fireOnExpand: false,
-      })
-    },
-  },
+    dropdownMatchSelectWidth: PropTypes.bool,
 
-  updated () {
-    this.$nextTick(() => {
-      this.setDropdownWidth()
-    })
+    // Pass by Select
+    isMultiple: PropTypes.bool,
+    dropdownPrefixCls: PropTypes.string,
+    dropdownVisibleChange: PropTypes.func,
+    popupElement: PropTypes.node,
+    open: PropTypes.bool,
+  },
+  created () {
+    this.triggerRef = createRef()
   },
   methods: {
-    onExpand (expandedKeys) {
-      // rerender
-      this.setState({
-        sExpandedKeys: expandedKeys,
-        fireOnExpand: true,
-      }, () => {
-        // Fix https://github.com/ant-design/ant-design/issues/5689
-        if (this.$refs.trigger && this.$refs.trigger.forcePopupAlign) {
-          this.$refs.trigger.forcePopupAlign()
-        }
-      })
-    },
-
-    setDropdownWidth () {
-      const width = this.$el.offsetWidth
-      if (width !== this.dropdownWidth) {
-        this.setState({ dropdownWidth: width })
-      }
-    },
-
-    getPopupEleRefs () {
-      return this.$refs.popupEle
-    },
-
-    getPopupDOMNode () {
-      return this.$refs.trigger.getPopupDomNode()
-    },
-
     getDropdownTransitionName () {
-      const props = this.$props
-      let transitionName = props.transitionName
-      if (!transitionName && props.animation) {
-        transitionName = `${this.getDropdownPrefixCls()}-${props.animation}`
+      const { transitionName, animation, dropdownPrefixCls } = this.$props
+      if (!transitionName && animation) {
+        return `${dropdownPrefixCls}-${animation}`
       }
       return transitionName
     },
 
-    getDropdownPrefixCls () {
-      return `${this.prefixCls}-dropdown`
-    },
-
-    highlightTreeNode (treeNode) {
-      const props = this.$props
-      const filterVal = treeNode.$props[labelCompatible(props.treeNodeFilterProp)]
-      if (typeof filterVal === 'string') {
-        return props.inputValue && filterVal.indexOf(props.inputValue) > -1
+    forcePopupAlign () {
+      const $trigger = this.triggerRef.current
+      if ($trigger) {
+        $trigger.forcePopupAlign()
       }
-      return false
-    },
-
-    filterTreeNode_  (input, child) {
-      if (!input) {
-        return true
-      }
-      const filterTreeNode = this.filterTreeNode
-      if (!filterTreeNode) {
-        return true
-      }
-      const props = getAllProps(child)
-      if (props && props.disabled) {
-        return false
-      }
-      return filterTreeNode.call(this, input, child)
-    },
-
-    processTreeNode (treeNodes) {
-      const filterPoss = []
-      this._expandedKeys = []
-      loopAllChildren(treeNodes, (child, index, pos) => {
-        if (this.filterTreeNode_(this.inputValue, child)) {
-          filterPoss.push(pos)
-          this._expandedKeys.push(String(getKey(child)))
-        }
-      })
-
-      // Include the filtered nodes's ancestral nodes.
-      const processedPoss = []
-      filterPoss.forEach(pos => {
-        const arr = pos.split('-')
-        arr.reduce((pre, cur) => {
-          const res = `${pre}-${cur}`
-          if (processedPoss.indexOf(res) < 0) {
-            processedPoss.push(res)
-          }
-          return res
-        })
-      })
-      const filterNodesPositions = []
-      loopAllChildren(treeNodes, (child, index, pos) => {
-        if (processedPoss.indexOf(pos) > -1) {
-          filterNodesPositions.push({ node: child, pos })
-        }
-      })
-
-      const hierarchyNodes = flatToHierarchy(filterNodesPositions)
-
-      const recursive = children => {
-        return children.map(child => {
-          if (child.children) {
-            return cloneElement(child.node, {
-              children: recursive(child.children),
-            })
-          }
-          return child.node
-        })
-      }
-      return recursive(hierarchyNodes)
-    },
-    onSelect () {
-      this.__emit('select', ...arguments)
-    },
-
-    renderTree (keys, halfCheckedKeys, newTreeNodes, multiple) {
-      const props = this.$props
-
-      const trProps = {
-        multiple,
-        prefixCls: `${props.prefixCls}-tree`,
-        showIcon: props.treeIcon,
-        showLine: props.treeLine,
-        defaultExpandAll: props.treeDefaultExpandAll,
-        defaultExpandedKeys: props.treeDefaultExpandedKeys,
-        filterTreeNode: this.highlightTreeNode,
-      }
-      const trListeners = {}
-
-      if (props.treeCheckable) {
-        trProps.selectable = false
-        trProps.checkable = props.treeCheckable
-        trListeners.check = this.onSelect
-        trProps.checkStrictly = props.treeCheckStrictly
-        if (props.inputValue) {
-          // enable checkStrictly when search tree.
-          trProps.checkStrictly = true
-        } else {
-          trProps._treeNodesStates = props._treeNodesStates
-        }
-        if (trProps.treeCheckStrictly && halfCheckedKeys.length) {
-          trProps.checkedKeys = { checked: keys, halfChecked: halfCheckedKeys }
-        } else {
-          trProps.checkedKeys = keys
-        }
-      } else {
-        trProps.selectedKeys = keys
-        trListeners.select = this.onSelect
-      }
-
-      // expand keys
-      if (!trProps.defaultExpandAll && !trProps.defaultExpandedKeys && !props.loadData) {
-        trProps.expandedKeys = keys
-      }
-      trProps.autoExpandParent = true
-      trListeners.expand = this.onExpand
-      if (this._expandedKeys && this._expandedKeys.length) {
-        trProps.expandedKeys = this._expandedKeys
-      }
-      if (this.fireOnExpand) {
-        trProps.expandedKeys = this.sExpandedKeys
-        trProps.autoExpandParent = false
-      }
-
-      // async loadData
-      if (props.loadData) {
-        trProps.loadData = props.loadData
-      }
-      return (
-        <Tree ref='popupEle' {...{ props: trProps, on: trListeners }}>
-          {newTreeNodes}
-        </Tree>
-      )
     },
   },
 
   render () {
-    const props = this.$props
-    const multiple = props.multiple
-    const dropdownPrefixCls = this.getDropdownPrefixCls()
-    const popupClassName = {
-      [props.dropdownClassName]: !!props.dropdownClassName,
-      [`${dropdownPrefixCls}--${multiple ? 'multiple' : 'single'}`]: 1,
-    }
-    let visible = props.visible
-    const search = multiple || !props.showSearch ? null : (
-      <span class={`${dropdownPrefixCls}-search`}>{props.inputElement}</span>
-    )
-    const recursive = children => {
-      return children.map(function handler(child) { // eslint-disable-line
-        // if (isEmptyElement(child) || (child.data && child.data.slot)) {
-        //   return null
-        // }
-        if (!getSlotOptions(child).__ANT_TREE_SELECT_NODE) {
-          return null
-        }
-        const treeNodeProps = {
-          ...child.data,
-          props: {
-            ...getAllProps(child),
-            switcherIcon: props.switcherIcon,
-            title: getComponentFromProp(child, 'title') || getComponentFromProp(child, 'label'),
-          },
-          key: String(child.key),
-        }
-        if (child && child.componentOptions.children) {
-          // null or String has no Prop
-          return (
-            <TreeNode {...treeNodeProps}>
-              {recursive(child.componentOptions.children) }
-            </TreeNode>
-          )
-        }
-        return <TreeNode {...treeNodeProps} />
-      })
-    }
-    // const s = Date.now();
-    let treeNodes
-    if (props._cachetreeData && this.cacheTreeNodes) {
-      treeNodes = this.cacheTreeNodes
-    } else {
-      treeNodes = recursive(props.treeData || props.treeNodes)
-      this.cacheTreeNodes = treeNodes
-    }
-    // console.log(Date.now()-s);
+    const {
+      disabled, isMultiple,
+      dropdownPopupAlign, dropdownMatchSelectWidth, dropdownClassName,
+      dropdownStyle, dropdownVisibleChange, getPopupContainer,
+      dropdownPrefixCls, popupElement, open,
+    } = this.$props
 
-    if (props.inputValue) {
-      treeNodes = this.processTreeNode(treeNodes)
+    // TODO: [Legacy] Use new action when trigger fixed: https://github.com/react-component/trigger/pull/86
+
+    // When false do nothing with the width
+    // ref: https://github.com/ant-design/ant-design/issues/10927
+    let stretch
+    if (dropdownMatchSelectWidth !== false) {
+      stretch = dropdownMatchSelectWidth ? 'width' : 'minWidth'
     }
-
-    const keys = []
-    const halfCheckedKeys = []
-    loopAllChildren(treeNodes, (child) => {
-      if (props.value.some(item => item.value === getValuePropValue(child))) {
-        keys.push(String(getKey(child)))
-      }
-      if (props.halfCheckedValues &&
-        props.halfCheckedValues.some(item => item.value === getValuePropValue(child))) {
-        halfCheckedKeys.push(String(getKey(child)))
-      }
-    })
-
-    let notFoundContent
-    if (!treeNodes.length) {
-      if (props.notFoundContent) {
-        notFoundContent = (
-          <span class={`${props.prefixCls}-not-found`}>
-            {props.notFoundContent}
-          </span>
-        )
-      } else if (!search) {
-        visible = false
-      }
-    }
-    const popupElement = (
-      <div>
-        {search}
-        {notFoundContent || this.renderTree(keys, halfCheckedKeys, treeNodes, multiple)}
-      </div>
-    )
-
-    const popupStyle = { ...props.dropdownStyle }
-    const widthProp = props.dropdownMatchSelectWidth ? 'width' : 'minWidth'
-    if (this.dropdownWidth) {
-      popupStyle[widthProp] = `${this.dropdownWidth}px`
-    }
-
     return (
       <Trigger
-        action={props.disabled ? [] : ['click']}
-        ref='trigger'
+        {...{ directives: [{
+          name: 'ant-ref',
+          value: this.triggerRef,
+        }] }}
+        action={disabled ? [] : ['click']}
         popupPlacement='bottomLeft'
         builtinPlacements={BUILT_IN_PLACEMENTS}
-        popupAlign={props.dropdownPopupAlign}
+        popupAlign={dropdownPopupAlign}
         prefixCls={dropdownPrefixCls}
         popupTransitionName={this.getDropdownTransitionName()}
-        onPopupVisibleChange={props.dropdownVisibleChange}
+        onPopupVisibleChange={dropdownVisibleChange}
         popup={popupElement}
-        popupVisible={visible}
-        getPopupContainer={props.getPopupContainer}
-        popupClassName={classnames(popupClassName)}
-        popupStyle={popupStyle}
+        popupVisible={open}
+        getPopupContainer={getPopupContainer}
+        stretch={stretch}
+        popupClassName={classNames(
+          dropdownClassName,
+          {
+            [`${dropdownPrefixCls}--multiple`]: isMultiple,
+            [`${dropdownPrefixCls}--single`]: !isMultiple,
+          },
+        )}
+        popupStyle={dropdownStyle}
       >
         {this.$slots.default}
       </Trigger>
diff --git a/components/vc-tree-select/src/Selector/MultipleSelector/Selection.jsx b/components/vc-tree-select/src/Selector/MultipleSelector/Selection.jsx
new file mode 100644
index 000000000..43f7faa77
--- /dev/null
+++ b/components/vc-tree-select/src/Selector/MultipleSelector/Selection.jsx
@@ -0,0 +1,64 @@
+
+import PropTypes from '../../../../_util/vue-types'
+import {
+  toTitle,
+  UNSELECTABLE_ATTRIBUTE, UNSELECTABLE_STYLE,
+} from '../../util'
+import { getComponentFromProp } from '../../../../_util/props-util'
+import BaseMixin from '../../../../_util/BaseMixin'
+
+const Selection = {
+  mixins: [BaseMixin],
+  props: {
+    prefixCls: PropTypes.string,
+    maxTagTextLength: PropTypes.number,
+    // onRemove: PropTypes.func,
+
+    label: PropTypes.any,
+    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+    removeIcon: PropTypes.any,
+  },
+  methods: {
+    onRemove (event) {
+      const { value } = this.$props
+      this.__emit('remove', event, value)
+      event.stopPropagation()
+    },
+  },
+
+  render () {
+    const {
+      prefixCls, maxTagTextLength,
+      label, value,
+    } = this.$props
+    const { $listeners } = this
+    let content = label || value
+    if (maxTagTextLength && typeof content === 'string' && content.length > maxTagTextLength) {
+      content = `${content.slice(0, maxTagTextLength)}...`
+    }
+
+    return (
+      <li
+        style={UNSELECTABLE_STYLE}
+        {...{ attrs: UNSELECTABLE_ATTRIBUTE }}
+        role='menuitem'
+        class={`${prefixCls}-selection__choice`}
+        title={toTitle(label)}
+      >
+        {$listeners.remove &&
+          <span
+            class={`${prefixCls}-selection__choice__remove`}
+            onClick={this.onRemove}
+          >
+            {getComponentFromProp(this, 'removeIcon')}
+          </span>
+        }
+        <span class={`${prefixCls}-selection__choice__content`}>
+          {content}
+        </span>
+      </li>
+    )
+  },
+}
+
+export default Selection
diff --git a/components/vc-tree-select/src/Selector/MultipleSelector/index.jsx b/components/vc-tree-select/src/Selector/MultipleSelector/index.jsx
new file mode 100644
index 000000000..3088b1de1
--- /dev/null
+++ b/components/vc-tree-select/src/Selector/MultipleSelector/index.jsx
@@ -0,0 +1,192 @@
+import PropTypes from '../../../../_util/vue-types'
+import { createRef } from '../../util'
+import generateSelector, { selectorPropTypes } from '../../Base/BaseSelector'
+import SearchInput from '../../SearchInput'
+import Selection from './Selection'
+import { getComponentFromProp } from '../../../../_util/props-util'
+import getTransitionProps from '../../../../_util/getTransitionProps'
+import BaseMixin from '../../../../_util/BaseMixin'
+const TREE_SELECT_EMPTY_VALUE_KEY = 'RC_TREE_SELECT_EMPTY_VALUE_KEY'
+
+const Selector = generateSelector('multiple')
+
+// export const multipleSelectorContextTypes = {
+//   onMultipleSelectorRemove: PropTypes.func.isRequired,
+// }
+
+const MultipleSelector = {
+  mixins: [BaseMixin],
+  props: {
+    ...selectorPropTypes(),
+    ...SearchInput.props,
+    selectorValueList: PropTypes.array,
+    disabled: PropTypes.bool,
+    searchValue: PropTypes.string,
+    labelInValue: PropTypes.bool,
+    maxTagCount: PropTypes.number,
+    maxTagPlaceholder: PropTypes.any,
+
+    // onChoiceAnimationLeave: PropTypes.func,
+  },
+  inject: {
+    vcTreeSelect: { default: {}},
+  },
+  created () {
+    this.inputRef = createRef()
+  },
+  methods: {
+    onPlaceholderClick  () {
+      this.inputRef.current.focus()
+    },
+
+    focus () {
+      this.inputRef.current.focus()
+    },
+    blur  () {
+      this.inputRef.current.blur()
+    },
+
+    _renderPlaceholder  () {
+      const {
+        prefixCls,
+        placeholder, searchPlaceholder,
+        searchValue, selectorValueList,
+      } = this.$props
+
+      const currentPlaceholder = placeholder || searchPlaceholder
+
+      if (!currentPlaceholder) return null
+
+      const hidden = searchValue || selectorValueList.length
+
+      // [Legacy] Not remove the placeholder
+      return (
+        <span
+          style={{
+            display: hidden ? 'none' : 'block',
+          }}
+          onClick={this.onPlaceholderClick}
+          class={`${prefixCls}-search__field__placeholder`}
+        >
+          {currentPlaceholder}
+        </span>
+      )
+    },
+    onChoiceAnimationLeave (...args) {
+      this.__emit('choiceAnimationLeave', ...args)
+    },
+    renderSelection () {
+      const {
+        selectorValueList, choiceTransitionName, prefixCls,
+        labelInValue, maxTagCount,
+      } = this.$props
+      const { vcTreeSelect: { onMultipleSelectorRemove }, $listeners, $slots } = this
+
+      // Check if `maxTagCount` is set
+      let myValueList = selectorValueList
+      if (maxTagCount >= 0) {
+        myValueList = selectorValueList.slice(0, maxTagCount)
+      }
+      // Selector node list
+      const selectedValueNodes = myValueList.map(({ label, value }) => (
+        <Selection
+          {...{
+            props: {
+              ...this.$props,
+              label,
+              value,
+            },
+            on: { ...$listeners, remove: onMultipleSelectorRemove },
+          }}
+          key={value || TREE_SELECT_EMPTY_VALUE_KEY}
+        >{$slots.default}</Selection>
+      ))
+
+      // Rest node count
+      if (maxTagCount >= 0 && maxTagCount < selectorValueList.length) {
+        let content = `+ ${selectorValueList.length - maxTagCount} ...`
+        const maxTagPlaceholder = getComponentFromProp(this, 'maxTagPlaceholder', {}, false)
+        if (typeof maxTagPlaceholder === 'string') {
+          content = maxTagPlaceholder
+        } else if (typeof maxTagPlaceholder === 'function') {
+          const restValueList = selectorValueList.slice(maxTagCount)
+          content = maxTagPlaceholder(
+            labelInValue ? restValueList : restValueList.map(({ value }) => value)
+          )
+        }
+
+        const restNodeSelect = (
+          <Selection
+            {...{
+              props: {
+                ...this.$props,
+                label: content,
+                value: null,
+              },
+              on: $listeners,
+            }}
+            key='rc-tree-select-internal-max-tag-counter'
+          >{$slots.default}</Selection>
+        )
+
+        selectedValueNodes.push(restNodeSelect)
+      }
+
+      selectedValueNodes.push(<li
+        class={`${prefixCls}-search ${prefixCls}-search--inline`}
+        key='__input'
+      >
+        <SearchInput {...{
+          props: {
+            ...this.$props,
+            needAlign: true,
+          },
+          on: $listeners,
+          directives: [{
+            name: 'ant-ref',
+            value: this.inputRef,
+          }],
+        }}>{$slots.default}</SearchInput>
+      </li>)
+      const className = `${prefixCls}-selection__rendered`
+      if (choiceTransitionName) {
+        const transitionProps = getTransitionProps(choiceTransitionName, {
+          tag: 'ul',
+          afterLeave: this.onChoiceAnimationLeave,
+        })
+        return (<transition-group
+          class={className}
+          {...transitionProps}
+        >
+          {selectedValueNodes}
+        </transition-group>)
+      }
+      return (
+        <ul class={className} role='menubar'>
+          {selectedValueNodes}
+        </ul>
+      )
+    },
+  },
+
+  render () {
+    const { $listeners, $slots } = this
+    return (
+      <Selector
+        {...{
+          props: {
+            ...this.$props,
+            tabIndex: -1,
+            showArrow: false,
+            renderSelection: this.renderSelection,
+            renderPlaceholder: this._renderPlaceholder,
+          },
+          on: $listeners,
+        }}
+
+      >{$slots.default}</Selector>
+    )
+  },
+}
+
+export default MultipleSelector
diff --git a/components/vc-tree-select/src/Selector/SingleSelector.jsx b/components/vc-tree-select/src/Selector/SingleSelector.jsx
new file mode 100644
index 000000000..30ff09f4e
--- /dev/null
+++ b/components/vc-tree-select/src/Selector/SingleSelector.jsx
@@ -0,0 +1,75 @@
+import generateSelector, { selectorPropTypes } from '../Base/BaseSelector'
+import { toTitle } from '../util'
+import { getOptionProps } from '../../../_util/props-util'
+import { createRef } from '../util'
+const Selector = generateSelector('single')
+
+const SingleSelector = {
+  name: 'SingleSelector',
+  props: selectorPropTypes(),
+  created () {
+    this.selectorRef = createRef()
+  },
+  methods: {
+    focus  () {
+      this.selectorRef.current.focus()
+    },
+    blur  () {
+      this.selectorRef.current.blur()
+    },
+    renderSelection  () {
+      const { selectorValueList, placeholder, prefixCls } = this.$props
+
+      let innerNode
+
+      if (selectorValueList.length) {
+        const { label, value } = selectorValueList[0]
+        innerNode = (
+          <span
+            key='value'
+            title={toTitle(label)}
+            class={`${prefixCls}-selection-selected-value`}
+          >
+            {label || value}
+          </span>
+        )
+      } else {
+        innerNode = (
+          <span
+            key='placeholder'
+            class={`${prefixCls}-selection__placeholder`}
+          >
+            {placeholder}
+          </span>
+        )
+      }
+
+      return (
+        <span class={`${prefixCls}-selection__rendered`}>
+          {innerNode}
+        </span>
+      )
+    },
+  },
+
+  render () {
+    const props = {
+      props: {
+        ...getOptionProps(this),
+        renderSelection: this.renderSelection,
+      },
+      on: this.$listeners,
+      directives: [{
+        name: 'ant-ref',
+        value: this.selectorRef,
+      }],
+    }
+    return (
+      <Selector
+        {...props}
+      />
+    )
+  },
+}
+
+export default SingleSelector
diff --git a/components/vc-tree-select/src/TreeNode.jsx b/components/vc-tree-select/src/TreeNode.jsx
deleted file mode 100644
index 9a9d2d195..000000000
--- a/components/vc-tree-select/src/TreeNode.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { TreeNode } from '../../vc-tree'
-export default {
-  name: 'TreeNode',
-  __ANT_TREE_SELECT_NODE: true,
-  props: {
-    ...TreeNode.props,
-    value: String,
-  },
-  render () {
-    return this
-  },
-}
diff --git a/components/vc-tree-select/src/index.js b/components/vc-tree-select/src/index.js
index 27d435e50..9c673b623 100644
--- a/components/vc-tree-select/src/index.js
+++ b/components/vc-tree-select/src/index.js
@@ -1,26 +1,7 @@
-// export this package's api
-import TreeSelect from './Select'
-import TreeNode from './TreeNode'
-import omit from 'omit.js'
-import { SHOW_ALL, SHOW_PARENT, SHOW_CHILD } from './strategies'
-TreeSelect.TreeNode = TreeNode
+import Select from './Select'
+import SelectNode from './SelectNode'
 
-export default {
-  functional: true,
-  render (h, context) {
-    const { props, listeners, children = [], data } = context
-    const treeSelectProps = {
-      ...omit(data, ['attrs']),
-      props: {
-        ...props,
-        children,
-        __propsSymbol__: Symbol(),
-      },
-      on: listeners,
-    }
-    return <TreeSelect {...treeSelectProps}/>
-  },
-  TreeNode,
-  SHOW_ALL, SHOW_PARENT, SHOW_CHILD,
-}
-export { TreeNode, SHOW_ALL, SHOW_PARENT, SHOW_CHILD }
+export { SHOW_ALL, SHOW_CHILD, SHOW_PARENT } from './strategies'
+export const TreeNode = SelectNode
+
+export default Select
diff --git a/components/vc-tree-select/src/propTypes.js b/components/vc-tree-select/src/propTypes.js
new file mode 100644
index 000000000..2afe88e41
--- /dev/null
+++ b/components/vc-tree-select/src/propTypes.js
@@ -0,0 +1,46 @@
+import PropTypes from '../../_util/vue-types'
+import { isLabelInValue } from './util'
+
+const internalValProp = PropTypes.oneOfType([
+  PropTypes.string,
+  PropTypes.number,
+])
+
+export function genArrProps (propType) {
+  return PropTypes.oneOfType([
+    propType,
+    PropTypes.arrayOf(propType),
+  ])
+}
+
+/**
+ * Origin code check `multiple` is true when `treeCheckStrictly` & `labelInValue`.
+ * But in process logic is already cover to array.
+ * Check array is not necessary. Let's simplify this check logic.
+ */
+export function valueProp (...args) {
+  const [props, propName, Component] = args
+
+  if (isLabelInValue(props)) {
+    const err = genArrProps(PropTypes.shape({
+      label: PropTypes.node,
+      value: internalValProp,
+    }).loose)(...args)
+    if (err) {
+      return new Error(
+        `Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
+        `You should use { label: string, value: string | number } or [{ label: string, value: string | number }] instead.`
+      )
+    }
+    return null
+  }
+
+  const err = genArrProps(internalValProp)(...args)
+  if (err) {
+    return new Error(
+      `Invalid prop \`${propName}\` supplied to \`${Component}\`. ` +
+      `You should use string or [string] instead.`
+    )
+  }
+  return null
+}
diff --git a/components/vc-tree-select/src/util.js b/components/vc-tree-select/src/util.js
index 4b7ebca91..4ee1ded02 100644
--- a/components/vc-tree-select/src/util.js
+++ b/components/vc-tree-select/src/util.js
@@ -1,5 +1,17 @@
-import { getPropsData, getAllProps, getKey, getAttrs, getSlotOptions, getSlots } from '../../_util/props-util'
-import { cloneVNodes, cloneElement } from '../../_util/vnode'
+import warning from 'warning'
+import omit from 'omit.js'
+import {
+  convertDataToTree as vcConvertDataToTree,
+  convertTreeToEntities as vcConvertTreeToEntities,
+  conductCheck as rcConductCheck,
+} from '../../vc-tree/src/util'
+import SelectNode from './SelectNode'
+import { SHOW_CHILD, SHOW_PARENT } from './strategies'
+import { getSlots, getPropsData } from '../../_util/props-util'
+
+let warnDeprecatedLabel = false
+
+// =================== MISC ====================
 export function toTitle (title) {
   if (typeof title === 'string') {
     return title
@@ -7,58 +19,20 @@ export function toTitle (title) {
   return null
 }
 
-export function getValuePropValue (child) {
-  const props = getAllProps(child)
-  if ('value' in props) {
-    return props.value
-  }
-  if (getKey(child) !== undefined) {
-    return getKey(child)
-  }
-  throw new Error(`no key or value for ${child}`)
+export function toArray (data) {
+  if (!data) return []
+
+  return Array.isArray(data) ? data : [data]
 }
 
-export function getPropValue (child, prop) {
-  if (prop === 'value') {
-    return getValuePropValue(child)
-  }
-  const slots = getSlots(child)
-  if (prop === 'children') {
-    const newChild = child.$slots ? cloneVNodes(child.$slots.default, true) : cloneVNodes(child.componentOptions.children, true)
-    if (newChild.length === 1 && !newChild[0].tag) {
-      return newChild[0].text
-    }
-    return newChild
-  }
-  if (slots[prop]) {
-    return cloneVNodes(slots[prop], true)
-  }
-  const data = getPropsData(child)
-  if (prop in data) {
-    return data[prop]
-  } else {
-    return getAttrs(child)[prop]
+export function createRef () {
+  const func = function setRef (node) {
+    func.current = node
   }
+  return func
 }
 
-export function isMultiple (props) {
-  return !!(props.multiple || props.treeCheckable)
-}
-
-export function toArray (value) {
-  let ret = value
-  if (value === undefined) {
-    ret = []
-  } else if (!Array.isArray(value)) {
-    ret = [value]
-  }
-  return ret
-}
-
-export function preventDefaultEvent (e) {
-  e.preventDefault()
-}
-
+// =============== Legacy ===============
 export const UNSELECTABLE_STYLE = {
   userSelect: 'none',
   WebkitUserSelect: 'none',
@@ -68,502 +42,389 @@ export const UNSELECTABLE_ATTRIBUTE = {
   unselectable: 'unselectable',
 }
 
-export function labelCompatible (prop) {
-  let newProp = prop
-  if (newProp === 'label') {
-    newProp = 'title'
+/**
+ * Convert position list to hierarchy structure.
+ * This is little hack since use '-' to split the position.
+ */
+export function flatToHierarchy (positionList) {
+  if (!positionList.length) {
+    return []
   }
-  return newProp
-}
 
-export function isInclude (smallArray, bigArray) {
-  // attention: [0,0,1] [0,0,10]
-  return smallArray.every((ii, i) => {
-    return ii === bigArray[i]
-  })
-}
+  const entrances = {}
 
-export function isPositionPrefix (smallPos, bigPos) {
-  if (!bigPos || !smallPos) {
-    // console.log(smallPos, bigPos);
-    return false
-  }
-  if (bigPos.length < smallPos.length) {
-    return false
-  }
-  // attention: "0-0-1" "0-0-10"
-  if ((bigPos.length > smallPos.length) && (bigPos.charAt(smallPos.length) !== '-')) {
-    return false
-  }
-  return bigPos.substr(0, smallPos.length) === smallPos
-}
-
-/*
-export function getCheckedKeys(node, checkedKeys, allCheckedNodesKeys) {
-  const nodeKey = node.props.eventKey;
-  let newCks = [...checkedKeys];
-  let nodePos;
-  const unCheck = allCheckedNodesKeys.some(item => {
-    if (item.key === nodeKey) {
-      nodePos = item.pos;
-      return true;
+  // Prepare the position map
+  const posMap = {}
+  const parsedList = positionList.slice().map(entity => {
+    const clone = {
+      ...entity,
+      fields: entity.pos.split('-'),
     }
-  });
-  if (unCheck) {
-    newCks = [];
-    allCheckedNodesKeys.forEach(item => {
-      if (isPositionPrefix(item.pos, nodePos) || isPositionPrefix(nodePos, item.pos)) {
-        return;
-      }
-      newCks.push(item.key);
-    });
-  } else {
-    newCks.push(nodeKey);
-  }
-  return newCks;
-}
-*/
+    delete clone.children
+    return clone
+  })
 
-function getChildrenlength (children) {
-  let len = 1
-  if (Array.isArray(children)) {
-    len = children.length
-  }
-  return len
+  parsedList.forEach((entity) => {
+    posMap[entity.pos] = entity
+  })
+
+  parsedList.sort((a, b) => {
+    return a.fields.length - b.fields.length
+  })
+
+  // Create the hierarchy
+  parsedList.forEach((entity) => {
+    const parentPos = entity.fields.slice(0, -1).join('-')
+    const parentEntity = posMap[parentPos]
+
+    if (!parentEntity) {
+      entrances[entity.pos] = entity
+    } else {
+      parentEntity.children = parentEntity.children || []
+      parentEntity.children.push(entity)
+    }
+
+    // Some time position list provide `key`, we don't need it
+    delete entity.key
+    delete entity.fields
+  })
+
+  return Object.keys(entrances).map(key => entrances[key])
 }
 
-function getSiblingPosition (index, len, siblingPosition) {
-  if (len === 1) {
-    siblingPosition.first = true
-    siblingPosition.last = true
-  } else {
-    siblingPosition.first = index === 0
-    siblingPosition.last = index === len - 1
-  }
-  return siblingPosition
+// =============== Accessibility ===============
+let ariaId = 0
+
+export function resetAriaId () {
+  ariaId = 0
 }
 
-function filterChild (childs) {
-  const newChilds = []
-  childs.forEach(child => {
-    const options = getSlotOptions(child)
-    if (options.__ANT_TREE_NODE || options.__ANT_TREE_SELECT_NODE) {
-      newChilds.push(child)
+export function generateAriaId (prefix) {
+  ariaId += 1
+  return `${prefix}_${ariaId}`
+}
+
+export function isLabelInValue (props) {
+  const { treeCheckable, treeCheckStrictly, labelInValue } = props
+  if (treeCheckable && treeCheckStrictly) {
+    return true
+  }
+  return labelInValue || false
+}
+
+// =================== Tree ====================
+export function parseSimpleTreeData (treeData, { id, pId, rootPId }) {
+  const keyNodes = {}
+  const rootNodeList = []
+
+  // Fill in the map
+  const nodeList = treeData.map((node) => {
+    const clone = { ...node }
+    const key = clone[id]
+    keyNodes[key] = clone
+    clone.key = clone.key || key
+    return clone
+  })
+
+  // Connect tree
+  nodeList.forEach((node) => {
+    const parentKey = node[pId]
+    const parent = keyNodes[parentKey]
+
+    // Fill parent
+    if (parent) {
+      parent.children = parent.children || []
+      parent.children.push(node)
+    }
+
+    // Fill root tree node
+    if (parentKey === rootPId || (!parent && rootPId === null)) {
+      rootNodeList.push(node)
     }
   })
-  return newChilds
+
+  return rootNodeList
 }
 
-export function loopAllChildren (childs, callback, parent) {
-  const loop = (children, level, _parent) => {
-    const len = getChildrenlength(children)
-    children.forEach(function handler(item, index) { // eslint-disable-line
-      const pos = `${level}-${index}`
-      if (item && item.componentOptions && item.componentOptions.children) {
-        loop(filterChild(item.componentOptions.children), pos, { node: item, pos })
-      }
-      if (item) {
-        callback(item, index, pos, item.key || pos, getSiblingPosition(index, len, {}), _parent)
+/**
+ * Detect if position has relation.
+ * e.g. 1-2 related with 1-2-3
+ * e.g. 1-3-2 related with 1
+ * e.g. 1-2 not related with 1-21
+ */
+export function isPosRelated (pos1, pos2) {
+  const fields1 = pos1.split('-')
+  const fields2 = pos2.split('-')
+
+  const minLen = Math.min(fields1.length, fields2.length)
+  for (let i = 0; i < minLen; i += 1) {
+    if (fields1[i] !== fields2[i]) {
+      return false
+    }
+  }
+  return true
+}
+
+/**
+ * This function is only used on treeNode check (none treeCheckStrictly but has searchInput).
+ * We convert entity to { node, pos, children } format.
+ * This is legacy bug but we still need to do with it.
+ * @param entity
+ */
+export function cleanEntity ({ node, pos, children }) {
+  const instance = {
+    node,
+    pos,
+  }
+
+  if (children) {
+    instance.children = children.map(cleanEntity)
+  }
+
+  return instance
+}
+
+/**
+ * Get a filtered TreeNode list by provided treeNodes.
+ * [Legacy] Since `Tree` use `key` as map but `key` will changed by React,
+ * we have to convert `treeNodes > data > treeNodes` to keep the key.
+ * Such performance hungry!
+ */
+export function getFilterTree (h, treeNodes, searchValue, filterFunc, valueEntities) {
+  if (!searchValue) {
+    return null
+  }
+
+  function mapFilteredNodeToData (node) {
+    if (!node) return null
+
+    let match = false
+    if (filterFunc(searchValue, node)) {
+      match = true
+    }
+    const $slots = getSlots(node)
+    const children = ($slots.default || []).map(mapFilteredNodeToData).filter(n => n)
+    delete $slots.default
+    const slotsKey = Object.keys($slots)
+    if (children.length || match) {
+      return (
+        <SelectNode
+          {...node.data}
+          key={valueEntities[getPropsData(node).value].key}
+        >
+          {children}
+          {slotsKey.length ? slotsKey.map(name => {
+            return <template slot={name}>{$slots[name][0].tag === 'template' ? $slots[name][0].children : $slots[name]}</template>
+          }) : null}
+        </SelectNode>
+      )
+    }
+
+    return null
+  }
+  return treeNodes.map(mapFilteredNodeToData).filter(node => node)
+}
+
+// =================== Value ===================
+/**
+ * Convert value to array format to make logic simplify.
+ */
+export function formatInternalValue (value, props) {
+  const valueList = toArray(value)
+
+  // Parse label in value
+  if (isLabelInValue(props)) {
+    return valueList.map((val) => {
+      if (typeof val !== 'object' || !val) {
+        return {
+          value: '',
+          label: '',
+        }
       }
+
+      return val
     })
   }
-  loop(filterChild(childs), 0, parent)
+
+  return valueList.map(val => ({
+    value: val,
+  }))
 }
 
-// export function loopAllChildren(childs, callback) {
-//   const loop = (children, level) => {
-//     React.Children.forEach(children, (item, index) => {
-//       const pos = `${level}-${index}`;
-//       if (item && item.props.children) {
-//         loop(item.props.children, pos);
-//       }
-//       if (item) {
-//         callback(item, index, pos, getValuePropValue(item));
-//       }
-//     });
-//   };
-//   loop(childs, 0);
-// }
-
-// TODO: Here has the side effect. Update node children data affect.
-export function flatToHierarchy (arr) {
-  if (!arr.length) {
-    return arr
+export function getLabel (wrappedValue, entity, treeNodeLabelProp) {
+  if (wrappedValue.label) {
+    return wrappedValue.label
   }
-  const hierarchyNodes = []
-  const levelObj = {}
-  arr.forEach((item) => {
-    if (!item.pos) {
-      return
+
+  if (entity) {
+    const props = getPropsData(entity.node)
+    if (Object.keys(props).length) {
+      return props[treeNodeLabelProp]
     }
-    const posLen = item.pos.split('-').length
-    if (!levelObj[posLen]) {
-      levelObj[posLen] = []
-    }
-    levelObj[posLen].push(item)
-  })
-  const levelArr = Object.keys(levelObj).sort((a, b) => b - a)
-  // const s = Date.now();
-  // todo: there are performance issues!
-  levelArr.reduce((pre, cur) => {
-    if (cur && cur !== pre) {
-      levelObj[pre].forEach((item) => {
-        let haveParent = false
-        levelObj[cur].forEach((ii) => {
-          if (isPositionPrefix(ii.pos, item.pos)) {
-            haveParent = true
-            if (!ii.children) {
-              ii.children = []
-            }
-            ii.children.push(item)
-          }
+  }
+
+  // Since value without entity will be in missValueList.
+  // This code will never reached, but we still need this in case.
+  return wrappedValue.value
+}
+
+/**
+ * Convert internal state `valueList` to user needed value list.
+ * This will return an array list. You need check if is not multiple when return.
+ *
+ * `allCheckedNodes` is used for `treeCheckStrictly`
+ */
+export function formatSelectorValue (valueList, props, valueEntities) {
+  const {
+    treeNodeLabelProp,
+    treeCheckable, treeCheckStrictly, showCheckedStrategy,
+  } = props
+
+  // Will hide some value if `showCheckedStrategy` is set
+  if (treeCheckable && !treeCheckStrictly) {
+    const values = {}
+    valueList.forEach((wrappedValue) => {
+      values[wrappedValue.value] = wrappedValue
+    })
+    const hierarchyList = flatToHierarchy(valueList.map(({ value }) => valueEntities[value]))
+
+    if (showCheckedStrategy === SHOW_PARENT) {
+      // Only get the parent checked value
+      return hierarchyList.map(({ node }) => {
+        const value = getPropsData(node).value
+        return {
+          label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
+          value,
+        }
+      })
+    } else if (showCheckedStrategy === SHOW_CHILD) {
+      // Only get the children checked value
+      const targetValueList = []
+
+      // Find the leaf children
+      const traverse = ({ node, children }) => {
+        const value = getPropsData(node).value
+        if (!children || children.length === 0) {
+          targetValueList.push({
+            label: getLabel(values[value], valueEntities[value], treeNodeLabelProp),
+            value,
+          })
+          return
+        }
+
+        children.forEach((entity) => {
+          traverse(entity)
         })
-        if (!haveParent) {
-          hierarchyNodes.push(item)
-        }
-      })
-    }
-    return cur
-  })
-  // console.log(Date.now() - s);
-  return levelObj[levelArr[levelArr.length - 1]].concat(hierarchyNodes)
-}
+      }
 
-// arr.length === 628, use time: ~20ms
-export function filterParentPosition (arr) {
-  const levelObj = {}
-  arr.forEach((item) => {
-    const posLen = item.split('-').length
-    if (!levelObj[posLen]) {
-      levelObj[posLen] = []
-    }
-    levelObj[posLen].push(item)
-  })
-  const levelArr = Object.keys(levelObj).sort()
-  for (let i = 0; i < levelArr.length; i++) {
-    if (levelArr[i + 1]) {
-      levelObj[levelArr[i]].forEach(ii => {
-        for (let j = i + 1; j < levelArr.length; j++) {
-          levelObj[levelArr[j]].forEach((_i, index) => {
-            if (isPositionPrefix(ii, _i)) {
-              levelObj[levelArr[j]][index] = null
-            }
-          })
-          levelObj[levelArr[j]] = levelObj[levelArr[j]].filter(p => p)
-        }
+      hierarchyList.forEach((entity) => {
+        traverse(entity)
       })
+
+      return targetValueList
     }
   }
-  let nArr = []
-  levelArr.forEach(i => {
-    nArr = nArr.concat(levelObj[i])
-  })
-  return nArr
-}
-// console.log(filterParentPosition(
-// ['0-2', '0-3-3', '0-10', '0-10-0', '0-0-1', '0-0', '0-1-1', '0-1']
-// ));
 
-function stripTail (str) {
-  const arr = str.match(/(.+)(-[^-]+)$/)
-  let st = ''
-  if (arr && arr.length === 3) {
-    st = arr[1]
+  return valueList.map(wrappedValue => ({
+    label: getLabel(wrappedValue, valueEntities[wrappedValue.value], treeNodeLabelProp),
+    value: wrappedValue.value,
+  }))
+}
+
+/**
+ * Use `rc-tree` convertDataToTree to convert treeData to TreeNodes.
+ * This will change the label to title value
+ */
+function processProps (props) {
+  const { title, label, key, value, class: cls, style, on = {}} = props
+  const p = {
+    props: omit(props, ['on', 'key', 'class', 'className', 'style']),
+    on,
+    class: cls || props.className,
+    style: style,
+    key: typeof key === 'number' ? String(key) : (key || value),
   }
-  return st
-}
-function splitPosition (pos) {
-  return pos.split('-')
-}
-
-// todo: do optimization.
-export function handleCheckState (obj, checkedPositionArr, checkIt) {
-  // console.log(stripTail('0-101-000'));
-  // let s = Date.now();
-  let objKeys = Object.keys(obj)
-
-  objKeys.forEach((i, index) => {
-    const iArr = splitPosition(i)
-    let saved = false
-    checkedPositionArr.forEach((_pos) => {
-      const _posArr = splitPosition(_pos)
-      if (iArr.length > _posArr.length && isInclude(_posArr, iArr)) {
-        obj[i].halfChecked = false
-        obj[i].checked = checkIt
-        objKeys[index] = null
-      }
-      if (iArr[0] === _posArr[0] && iArr[1] === _posArr[1]) {
-        saved = true
-      }
-    })
-    if (!saved) {
-      objKeys[index] = null
+  // Warning user not to use deprecated label prop.
+  if (label && !title) {
+    if (!warnDeprecatedLabel) {
+      warning(
+        false,
+        '\'label\' in treeData is deprecated. Please use \'title\' instead.'
+      )
+      warnDeprecatedLabel = true
     }
-  })
-  objKeys = objKeys.filter(i => i) // filter non null;
 
-  for (let pIndex = 0; pIndex < checkedPositionArr.length; pIndex++) {
-    // loop to set ancestral nodes's `checked` or `halfChecked`
-    const loop = (__pos) => {
-      const _posLen = splitPosition(__pos).length
-      if (_posLen <= 2) { // e.g. '0-0', '0-1'
-        return
-      }
-      let sibling = 0
-      let siblingChecked = 0
-      const parentPosition = stripTail(__pos)
-      objKeys.forEach((i /* , index*/) => {
-        const iArr = splitPosition(i)
-        if (iArr.length === _posLen && isInclude(splitPosition(parentPosition), iArr)) {
-          sibling++
-          if (obj[i].checked) {
-            siblingChecked++
-            const _i = checkedPositionArr.indexOf(i)
-            if (_i > -1) {
-              checkedPositionArr.splice(_i, 1)
-              if (_i <= pIndex) {
-                pIndex--
-              }
-            }
-          } else if (obj[i].halfChecked) {
-            siblingChecked += 0.5
-          }
-          // objKeys[index] = null;
-        }
-      })
-      // objKeys = objKeys.filter(i => i); // filter non null;
-      const parent = obj[parentPosition]
-      // not check, checked, halfChecked
-      if (siblingChecked === 0) {
-        parent.checked = false
-        parent.halfChecked = false
-      } else if (siblingChecked === sibling) {
-        parent.checked = true
-        parent.halfChecked = false
-      } else {
-        parent.halfChecked = true
-        parent.checked = false
-      }
-      loop(parentPosition)
-    }
-    loop(checkedPositionArr[pIndex], pIndex)
+    p.props.title = label
   }
-  // console.log(Date.now()-s, objKeys.length, checkIt);
+
+  return p
 }
 
-function getCheck (treeNodesStates, checkedPositions) {
-  const halfCheckedKeys = []
-  const checkedKeys = []
-  const checkedNodes = []
-  Object.keys(treeNodesStates).forEach((item) => {
-    const itemObj = treeNodesStates[item]
-    if (itemObj.checked) {
-      checkedKeys.push(itemObj.key)
-      // checkedNodes.push(getValuePropValue(itemObj.node));
-      checkedNodes.push({ ...itemObj, pos: item })
-    } else if (itemObj.halfChecked) {
-      halfCheckedKeys.push(itemObj.key)
-    }
-  })
+export function convertDataToTree (h, treeData) {
+  return vcConvertDataToTree(h, treeData, { processProps })
+}
+
+/**
+ * Use `rc-tree` convertTreeToEntities for entities calculation.
+ * We have additional entities of `valueEntities`
+ */
+function initWrapper (wrapper) {
   return {
-    halfCheckedKeys, checkedKeys, checkedNodes, treeNodesStates, checkedPositions,
+    ...wrapper,
+    valueEntities: {},
   }
 }
 
-export function getTreeNodesStates (children, values) {
-  const checkedPositions = []
-  const treeNodesStates = {}
-  loopAllChildren(children, (item, index, pos, keyOrPos, siblingPosition) => {
-    treeNodesStates[pos] = {
-      node: item,
-      key: keyOrPos,
-      checked: false,
-      halfChecked: false,
-      siblingPosition,
-    }
-    if (values.indexOf(getValuePropValue(item)) !== -1) {
-      treeNodesStates[pos].checked = true
-      checkedPositions.push(pos)
-    }
-  })
+function processEntity (entity, wrapper) {
+  const value = getPropsData(entity.node).value
+  entity.value = value
 
-  handleCheckState(treeNodesStates, filterParentPosition(checkedPositions.sort()), true)
-
-  return getCheck(treeNodesStates, checkedPositions)
+  // This should be empty, or will get error message.
+  const currentEntity = wrapper.valueEntities[value]
+  if (currentEntity) {
+    warning(
+      false,
+      `Conflict! value of node '${entity.key}' (${value}) has already used by node '${currentEntity.key}'.`
+    )
+  }
+  wrapper.valueEntities[value] = entity
 }
 
-// can add extra prop to every node.
-export function recursiveCloneChildren (children, cb = ch => ch) {
-  // return React.Children.map(children, child => {
-  return Array.from(children).map(child => {
-    const newChild = cb(child)
-    if (newChild && newChild.props && newChild.props.children) {
-      return cloneElement(newChild, {
-        children: recursiveCloneChildren(newChild.props.children, cb),
-      })
-    }
-    return newChild
-  })
-}
-// const newChildren = recursiveCloneChildren(children, child => {
-//   const extraProps = {};
-//   if (child && child.type && child.type.xxx) {
-//     extraProps._prop = true;
-//     return React.cloneElement(child, extraProps);
-//   }
-//   return child;
-// });
-
-function recursiveGen (children, level = 0) {
-  return children.map((child, index) => {
-    const pos = `${level}-${index}`
-    const props = getAllProps(child)
-    const { title, label, value, ...rest } = props
-    const { children: subChildren } = child.componentOptions
-    const o = {
-      ...rest,
-      title,
-      label: label || title,
-      value,
-      key: child.key,
-      _pos: pos,
-    }
-    if (subChildren) {
-      o.children = recursiveGen(subChildren, pos)
-    }
-    return o
+export function convertTreeToEntities (treeNodes) {
+  return vcConvertTreeToEntities(treeNodes, {
+    initWrapper,
+    processEntity,
   })
 }
 
-function recursive (children, cb) {
-  children.forEach(item => {
-    cb(item)
-    if (item.children) {
-      recursive(item.children, cb)
+/**
+ * https://github.com/ant-design/ant-design/issues/13328
+ * We need calculate the half check key when searchValue is set.
+ */
+// TODO: This logic may better move to rc-tree
+export function getHalfCheckedKeys (valueList, valueEntities) {
+  const values = {}
+
+  // Fill checked keys
+  valueList.forEach(({ value }) => {
+    values[value] = false
+  })
+
+  // Fill half checked keys
+  valueList.forEach(({ value }) => {
+    let current = valueEntities[value]
+
+    while (current && current.parent) {
+      const parentValue = current.parent.value
+      if (parentValue in values) break
+      values[parentValue] = true
+
+      current = current.parent
     }
   })
+
+  // Get half keys
+  return Object.keys(values).filter(value => values[value]).map(value => valueEntities[value].key)
 }
 
-// Get the tree's checkedNodes (todo: can merge to the `handleCheckState` function)
-// If one node checked, it's all children nodes checked.
-// If sibling nodes all checked, the parent checked.
-export function filterAllCheckedData (vs, treeNodes) {
-  const vals = [...vs]
-  if (!vals.length) {
-    return vals
-  }
-
-  const data = recursiveGen(treeNodes)
-  const checkedNodesPositions = []
-
-  function checkChildren (children) {
-    children.forEach(item => {
-      if (item.__checked) {
-        return
-      }
-      const ci = vals.indexOf(item.value)
-      const childs = item.children
-      if (ci > -1) {
-        item.__checked = true
-        checkedNodesPositions.push({ node: item, pos: item._pos })
-        vals.splice(ci, 1)
-        if (childs) {
-          recursive(childs, child => {
-            child.__checked = true
-            checkedNodesPositions.push({ node: child, pos: child._pos })
-          })
-        }
-      } else {
-        if (childs) {
-          checkChildren(childs)
-        }
-      }
-    })
-  }
-
-  function checkParent (children, parent = { root: true }) {
-    let siblingChecked = 0
-    children.forEach(item => {
-      const childs = item.children
-      if (childs && !item.__checked && !item.__halfChecked) {
-        const p = checkParent(childs, item)
-        if (p.__checked) {
-          siblingChecked++
-        } else if (p.__halfChecked) {
-          siblingChecked += 0.5
-        }
-      } else if (item.__checked) {
-        siblingChecked++
-      } else if (item.__halfChecked) {
-        siblingChecked += 0.5
-      }
-    })
-    const len = children.length
-    if (siblingChecked === len) {
-      parent.__checked = true
-      checkedNodesPositions.push({ node: parent, pos: parent._pos })
-    } else if (siblingChecked < len && siblingChecked > 0) {
-      parent.__halfChecked = true
-    }
-    if (parent.root) {
-      return children
-    }
-    return parent
-  }
-  checkChildren(data)
-  checkParent(data)
-
-  checkedNodesPositions.forEach((i, index) => {
-    // clear private metadata
-    delete checkedNodesPositions[index].node.__checked
-    delete checkedNodesPositions[index].node._pos
-    // create the same structure of `onCheck`'s return.
-    checkedNodesPositions[index].node.props = {
-      title: checkedNodesPositions[index].node.title,
-      label: checkedNodesPositions[index].node.label || checkedNodesPositions[index].node.title,
-      value: checkedNodesPositions[index].node.value,
-    }
-    if (checkedNodesPositions[index].node.children) {
-      checkedNodesPositions[index].node.props.children = checkedNodesPositions[index].node.children
-    }
-    delete checkedNodesPositions[index].node.title
-    delete checkedNodesPositions[index].node.label
-    delete checkedNodesPositions[index].node.value
-    delete checkedNodesPositions[index].node.children
-  })
-  return checkedNodesPositions
-}
-
-export function processSimpleTreeData (treeData, format) {
-  function unflatten2 (array, parent = { [format.id]: format.rootPId }) {
-    const children = []
-    for (let i = 0; i < array.length; i++) {
-      array[i] = { ...array[i] } // copy, can not corrupts original data
-      if (array[i][format.pId] === parent[format.id]) {
-        array[i].key = array[i][format.id]
-        children.push(array[i])
-        array.splice(i--, 1)
-      }
-    }
-    if (children.length) {
-      parent.children = children
-      children.forEach(child => unflatten2(array, child))
-    }
-    if (parent[format.id] === format.rootPId) {
-      return children
-    }
-  }
-  return unflatten2(treeData)
-}
-
-export function saveRef (instance, name) {
-  if (!instance.saveRefs) {
-    instance.saveRefs = {}
-  }
-  if (!instance.saveRefs[name]) {
-    instance.saveRefs[name] = (node) => {
-      instance[name] = node
-    }
-  }
-  return instance.saveRefs[name]
-}
+export const conductCheck = rcConductCheck
diff --git a/components/vc-tree/src/Tree.jsx b/components/vc-tree/src/Tree.jsx
index 60f9edbc3..43328ef7c 100644
--- a/components/vc-tree/src/Tree.jsx
+++ b/components/vc-tree/src/Tree.jsx
@@ -101,6 +101,8 @@ const Tree = {
   }),
 
   data () {
+    warning(this.$props.__propsSymbol__, 'must pass __propsSymbol__')
+    warning(this.$props.children, 'please children prop replace slots.default')
     this.needSyncKeys = {}
     const state = {
       _posEntities: {},
@@ -119,12 +121,6 @@ const Tree = {
     }
     return {
       ...state,
-      // ...this.getSyncProps(props),
-      // dragOverNodeKey: '',
-      // dropPosition: null,
-      // dragNodesKeys: [],
-      // sLoadedKeys: [],
-      // sLoadingKeys: [],
       ...this.getDerivedStateFromProps(getOptionProps(this), state),
     }
   },
@@ -478,7 +474,7 @@ const Tree = {
               event: 'load',
               node: treeNode,
             }
-            this.__emit('load', eventObj)
+            this.__emit('load', newLoadedKeys, eventObj)
             this.setUncontrolledState({
               _loadedKeys: newLoadedKeys,
             })
@@ -593,7 +589,6 @@ const Tree = {
 
       return cloneElement(child, {
         props: {
-          key,
           eventKey: key,
           expanded: expandedKeys.indexOf(key) !== -1,
           selected: selectedKeys.indexOf(key) !== -1,
@@ -608,6 +603,7 @@ const Tree = {
           dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
           dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
         },
+        key,
       })
     },
   },
diff --git a/components/vc-tree/src/TreeNode.jsx b/components/vc-tree/src/TreeNode.jsx
index bf10e6c4f..b16fa1a3d 100644
--- a/components/vc-tree/src/TreeNode.jsx
+++ b/components/vc-tree/src/TreeNode.jsx
@@ -46,6 +46,9 @@ const TreeNode = {
     icon: PropTypes.any,
     dataRef: PropTypes.object,
     switcherIcon: PropTypes.any,
+
+    label: PropTypes.any,
+    value: PropTypes.any,
   }, {}),
 
   data () {
diff --git a/components/vc-trigger/Popup.jsx b/components/vc-trigger/Popup.jsx
index 454cdbad7..a7b1d7ff8 100644
--- a/components/vc-trigger/Popup.jsx
+++ b/components/vc-trigger/Popup.jsx
@@ -4,8 +4,10 @@ import Align from '../vc-align'
 import PopupInner from './PopupInner'
 import LazyRenderBox from './LazyRenderBox'
 import animate from '../_util/css-animation'
+import BaseMixin from '../_util/BaseMixin'
 
 export default {
+  mixins: [BaseMixin],
   props: {
     visible: PropTypes.bool,
     getClassNameFromAlign: PropTypes.func,
@@ -165,7 +167,7 @@ export default {
 
         // Delay force align to makes ui smooth
         if (!stretchChecked) {
-          sizeStyle.visibility = 'hidden'
+          // sizeStyle.visibility = 'hidden'
           setTimeout(() => {
             if (this.$refs.alignInstance) {
               this.$refs.alignInstance.forceAlign()