From 263dd3a520d5e4f076d8349f1ea1178af47ff31d Mon Sep 17 00:00:00 2001
From: wangxueliang <wangxueliang@yidian-inc.com>
Date: Fri, 23 Mar 2018 18:37:32 +0800
Subject: [PATCH] add vc-slider v0.1

---
 components/vc-slider/src/Handle.jsx           | 153 +++--
 components/vc-slider/src/Range.jsx            | 591 +++++++++---------
 components/vc-slider/src/Slider.jsx           | 225 ++++---
 components/vc-slider/src/common/Marks.jsx     | 121 ++--
 components/vc-slider/src/common/Steps.jsx     |  43 +-
 .../vc-slider/src/common/createSlider.jsx     | 442 ++++++-------
 .../vc-slider/src/createSliderWithTooltip.jsx | 161 ++---
 components/vc-slider/src/utils.js             |   3 +-
 8 files changed, 845 insertions(+), 894 deletions(-)

diff --git a/components/vc-slider/src/Handle.jsx b/components/vc-slider/src/Handle.jsx
index 918ccc1e7..74fc1a8e4 100644
--- a/components/vc-slider/src/Handle.jsx
+++ b/components/vc-slider/src/Handle.jsx
@@ -1,74 +1,80 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import addEventListener from 'rc-util/lib/Dom/addEventListener';
 
-export default class Handle extends React.Component {
-  state = {
-    clickFocused: false,
-  }
+import classNames from 'classnames'
+import PropTypes from '../../../_util/vue-types'
+import addEventListener from '../../../_util/Dom/addEventListener'
+import BaseMixin from '../../../_util/BaseMixin'
 
-  componentDidMount() {
-    // mouseup won't trigger if mouse moved out of handle,
-    // so we listen on document here.
-    this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp);
-  }
-
-  componentWillUnmount() {
-    if (this.onMouseUpListener) {
-      this.onMouseUpListener.remove();
+export default {
+  mixins: [BaseMixin],
+  props: {
+    prefixCls: PropTypes.string,
+    vertical: PropTypes.bool,
+    offset: PropTypes.number,
+    disabled: PropTypes.bool,
+    min: PropTypes.number,
+    max: PropTypes.number,
+    value: PropTypes.number,
+    tabIndex: PropTypes.number,
+  },
+  data () {
+    return {
+      clickFocused: false,
     }
-  }
-
-  setClickFocus(focused) {
-    this.setState({ clickFocused: focused });
-  }
-
-  handleMouseUp = () => {
-    if (document.activeElement === this.handle) {
-      this.setClickFocus(true);
-    }
-  }
-
-  handleBlur = () => {
-    this.setClickFocus(false);
-  }
-
-  handleKeyDown = () => {
-    this.setClickFocus(false);
-  }
-
-  clickFocus() {
-    this.setClickFocus(true);
-    this.focus();
-  }
-
-  focus() {
-    this.handle.focus();
-  }
-
-  blur() {
-    this.handle.blur();
-  }
-
-  render() {
+  },
+  mounted () {
+    this.$nextTick(() => {
+      this.onMouseUpListener = addEventListener(document, 'mouseup', this.handleMouseUp)
+    })
+  },
+  beforeDestroy () {
+    this.$nextTick(() => {
+      if (this.onMouseUpListener) {
+        this.onMouseUpListener.remove()
+      }
+    })
+  },
+  methods: {
+    setClickFocus (focused) {
+      this.setState({ clickFocused: focused })
+    },
+    handleMouseUp () {
+      if (document.activeElement === this.$refs.handle) {
+        this.setClickFocus(true)
+      }
+    },
+    handleBlur () {
+      this.setClickFocus(false)
+    },
+    handleKeyDown () {
+      this.setClickFocus(false)
+    },
+    clickFocus () {
+      this.setClickFocus(true)
+      this.focus()
+    },
+    focus () {
+      this.$refs.handle.focus()
+    },
+    blur () {
+      this.$refs.handle.blur()
+    },
+  },
+  render () {
     const {
-      prefixCls, vertical, offset, style, disabled, min, max, value, tabIndex, ...restProps,
-    } = this.props;
+      prefixCls, vertical, offset, disabled, min, max, value, tabIndex, ...restProps
+    } = this.$props
 
     const className = classNames(
-      this.props.className,
       {
-        [`${prefixCls}-handle-click-focused`]: this.state.clickFocused,
+        [`${prefixCls}-handle-click-focused`]: this.clickFocused,
       }
-    );
+    )
 
-    const postionStyle = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` };
+    const postionStyle = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` }
     const elStyle = {
-      ...style,
       ...postionStyle,
-    };
-    let ariaProps = {};
+    }
+    let ariaProps = {}
     if (value !== undefined) {
       ariaProps = {
         ...ariaProps,
@@ -76,34 +82,21 @@ export default class Handle extends React.Component {
         'aria-valuemax': max,
         'aria-valuenow': value,
         'aria-disabled': !!disabled,
-      };
+      }
     }
 
     return (
       <div
-        ref={node => (this.handle = node)}
-        role="slider"
+        ref='handle'
+        role='slider'
         tabIndex= {disabled ? null : (tabIndex || 0)}
         {...ariaProps}
         {...restProps}
-        className={className}
+        class={className}
         style={elStyle}
         onBlur={this.handleBlur}
-        onKeyDown={this.handleKeyDown}
+        onKeydown={this.handleKeyDown}
       />
-    );
-  }
+    )
+  },
 }
-
-Handle.propTypes = {
-  prefixCls: PropTypes.string,
-  className: PropTypes.string,
-  vertical: PropTypes.bool,
-  offset: PropTypes.number,
-  style: PropTypes.object,
-  disabled: PropTypes.bool,
-  min: PropTypes.number,
-  max: PropTypes.number,
-  value: PropTypes.number,
-  tabIndex: PropTypes.number,
-};
diff --git a/components/vc-slider/src/Range.jsx b/components/vc-slider/src/Range.jsx
index 03876b052..7beeacf53 100644
--- a/components/vc-slider/src/Range.jsx
+++ b/components/vc-slider/src/Range.jsx
@@ -1,327 +1,308 @@
-/* eslint-disable react/prop-types */
-import React from 'react';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import shallowEqual from 'shallowequal';
-import Track from './common/Track';
-import createSlider from './common/createSlider';
-import * as utils from './utils';
+import classNames from 'classnames'
+import PropTypes from '../../../_util/vue-types'
+import BaseMixin from '../../../_util/BaseMixin'
+import { initDefaultProps, hasProp } from '../../../_util/props-util'
+import Track from './common/Track'
+import createSlider from './common/createSlider'
+import * as utils from './utils'
 
-class Range extends React.Component {
-  static displayName = 'Range';
-  static propTypes = {
-    defaultValue: PropTypes.arrayOf(PropTypes.number),
-    value: PropTypes.arrayOf(PropTypes.number),
-    count: PropTypes.number,
-    pushable: PropTypes.oneOfType([
-      PropTypes.bool,
-      PropTypes.number,
-    ]),
-    allowCross: PropTypes.bool,
-    disabled: PropTypes.bool,
-    tabIndex: PropTypes.arrayOf(PropTypes.number),
-  };
-
-  static defaultProps = {
+const rangeProps = {
+  defaultValue: PropTypes.arrayOf(PropTypes.number),
+  value: PropTypes.arrayOf(PropTypes.number),
+  count: PropTypes.number,
+  pushable: PropTypes.oneOfType([
+    PropTypes.bool,
+    PropTypes.number,
+  ]),
+  allowCross: PropTypes.bool,
+  disabled: PropTypes.bool,
+  tabIndex: PropTypes.arrayOf(PropTypes.number),
+}
+const Range = {
+  displayName: 'Range',
+  mixins: [BaseMixin],
+  props: initDefaultProps(rangeProps, {
     count: 1,
     allowCross: true,
     pushable: false,
     tabIndex: [],
-  };
-
-  constructor(props) {
-    super(props);
-
-    const { count, min, max } = props;
+  }),
+  data () {
+    const { count, min, max } = this
     const initialValue = Array.apply(null, Array(count + 1))
-            .map(() => min);
-    const defaultValue = 'defaultValue' in props ?
-            props.defaultValue : initialValue;
-    const value = props.value !== undefined ?
-            props.value : defaultValue;
-    const bounds = value.map((v, i) => this.trimAlignValue(v, i));
-    const recent = bounds[0] === max ? 0 : bounds.length - 1;
-
-    this.state = {
+      .map(() => min)
+    const defaultValue = hasProp(this, 'defaultValue') ? this.defaultValue : initialValue
+    let { value } = this
+    if (value === undefined) {
+      value = defaultValue
+    }
+    const bounds = value.map((v, i) => this.trimAlignValue(v, i))
+    const recent = bounds[0] === max ? 0 : bounds.length - 1
+    return {
       handle: null,
       recent,
       bounds,
-    };
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return;
-    if (this.props.min === nextProps.min &&
-        this.props.max === nextProps.max &&
-        shallowEqual(this.props.value, nextProps.value)) {
-      return;
     }
+  },
+  watch: {
+    value: {
+      handler (val) {
+        const { min, max } = this
+        this.setChangeValue(val, min, max)
+      },
+      deep: true,
+    },
+    min (val) {
+      const { bounds, max } = this
+      this.setChangeValue(bounds, val, max)
+    },
+    max (val) {
+      const { bounds, min } = this
+      this.setChangeValue(bounds, min, val)
+    },
+  },
+  methods: {
+    setChangeValue (value, min, max) {
+      const { bounds } = this
+      const newValue = value || bounds
+      const minAmaxProps = {
+        min,
+        max,
+      }
+      const nextBounds = newValue.map((v, i) => this.trimAlignValue(v, i, minAmaxProps))
+      if (nextBounds.length === bounds.length && nextBounds.every((v, i) => v === bounds[i])) return
 
-    const { bounds } = this.state;
-    const value = nextProps.value || bounds;
-    const nextBounds = value.map((v, i) => this.trimAlignValue(v, i, nextProps));
-    if (nextBounds.length === bounds.length && nextBounds.every((v, i) => v === bounds[i])) return;
+      this.setState({ bounds: nextBounds })
 
-    this.setState({ bounds: nextBounds });
+      if (bounds.some(v => utils.isValueOutOfRange(v, minAmaxProps))) {
+        const newValues = newValue.map((v) => {
+          return utils.ensureValueInRange(v, minAmaxProps)
+        })
+        this.$emit('change', newValues)
+      }
+    },
+    onChange (state) {
+      const isNotControlled = !hasProp(this, 'value')
+      if (isNotControlled) {
+        this.setState(state)
+      } else if (state.handle !== undefined) {
+        this.setState({ handle: state.handle })
+      }
 
-    if (bounds.some(v => utils.isValueOutOfRange(v, nextProps))) {
-      const newValues = value.map((v) => {
-        return utils.ensureValueInRange(v, nextProps);
-      });
-      this.props.onChange(newValues);
-    }
-  }
+      const data = { ...this.$data, ...state }
+      const changedValue = data.bounds
+      this.$emit('change', changedValue)
+    },
+    onStart (position) {
+      const { bounds } = this
+      this.$emit('beforeChange', bounds)
 
-  onChange(state) {
-    const props = this.props;
-    const isNotControlled = !('value' in props);
-    if (isNotControlled) {
-      this.setState(state);
-    } else if (state.handle !== undefined) {
-      this.setState({ handle: state.handle });
-    }
+      const value = this.calcValueByPos(position)
 
-    const data = { ...this.state, ...state };
-    const changedValue = data.bounds;
-    props.onChange(changedValue);
-  }
+      const closestBound = this.getClosestBound(value)
+      this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound)
 
-  onStart(position) {
-    const props = this.props;
-    const state = this.state;
-    const bounds = this.getValue();
-    props.onBeforeChange(bounds);
+      this.setState({
+        handle: this.prevMovedHandleIndex,
+        recent: this.prevMovedHandleIndex,
+      })
 
-    const value = this.calcValueByPos(position);
-    this.startValue = value;
-    this.startPosition = position;
+      const prevValue = bounds[this.prevMovedHandleIndex]
+      if (value === prevValue) return
 
-    const closestBound = this.getClosestBound(value);
-    this.prevMovedHandleIndex = this.getBoundNeedMoving(value, closestBound);
+      const nextBounds = [...bounds]
+      nextBounds[this.prevMovedHandleIndex] = value
+      this.$emit('change', { bounds: nextBounds })
+    },
+    onEnd () {
+      this.removeDocumentEvents()
+      this.$emit('afterChange', this.bounds)
+    },
+    onMove (e, position) {
+      utils.pauseEvent(e)
+      const { bounds, handle } = this
+      const value = this.calcValueByPos(position)
+      const oldValue = bounds[handle]
+      if (value === oldValue) return
 
-    this.setState({
-      handle: this.prevMovedHandleIndex,
-      recent: this.prevMovedHandleIndex,
-    });
+      this.moveTo(value)
+    },
+    onKeyboard (e) {
+      const valueMutator = utils.getKeyboardValueMutator(e)
 
-    const prevValue = bounds[this.prevMovedHandleIndex];
-    if (value === prevValue) return;
+      if (valueMutator) {
+        utils.pauseEvent(e)
+        const { bounds, handle } = this
+        const oldValue = bounds[handle]
+        const mutatedValue = valueMutator(oldValue, this.$props)
+        const value = this.trimAlignValue(mutatedValue)
+        if (value === oldValue) return
+        const isFromKeyboardEvent = true
+        this.moveTo(value, isFromKeyboardEvent)
+      }
+    },
+    getClosestBound (value) {
+      const { bounds } = this
+      let closestBound = 0
+      for (let i = 1; i < bounds.length - 1; ++i) {
+        if (value > bounds[i]) { closestBound = i }
+      }
+      if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) {
+        closestBound = closestBound + 1
+      }
+      return closestBound
+    },
+    getBoundNeedMoving (value, closestBound) {
+      const { bounds, recent } = this
+      let boundNeedMoving = closestBound
+      const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound])
 
-    const nextBounds = [...state.bounds];
-    nextBounds[this.prevMovedHandleIndex] = value;
-    this.onChange({ bounds: nextBounds });
-  }
+      if (isAtTheSamePoint && bounds[recent] === bounds[closestBound]) {
+        boundNeedMoving = recent
+      }
 
-  onEnd = () => {
-    this.removeDocumentEvents();
-    this.props.onAfterChange(this.getValue());
-  }
+      if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) {
+        boundNeedMoving = value < bounds[closestBound + 1] ? closestBound : closestBound + 1
+      }
+      return boundNeedMoving
+    },
+    getLowerBound () {
+      return this.bounds[0]
+    },
+    getUpperBound () {
+      const { bounds } = this
+      return bounds[bounds.length - 1]
+    },
+    /**
+     * Returns an array of possible slider points, taking into account both
+     * `marks` and `step`. The result is cached.
+     */
+    getPoints () {
+      const { marks, step, min, max } = this
+      const cache = this._getPointsCache
+      if (!cache || cache.marks !== marks || cache.step !== step) {
+        const pointsObject = { ...marks }
+        if (step !== null) {
+          for (let point = min; point <= max; point += step) {
+            pointsObject[point] = point
+          }
+        }
+        const points = Object.keys(pointsObject).map(parseFloat)
+        points.sort((a, b) => a - b)
+        this._getPointsCache = { marks, step, points }
+      }
+      return this._getPointsCache.points
+    },
+    moveTo (value, isFromKeyboardEvent) {
+      const { bounds, handle } = this
+      const nextBounds = [...bounds]
+      nextBounds[handle] = value
+      let nextHandle = handle
+      if (this.pushable !== false) {
+        this.pushSurroundingHandles(nextBounds, nextHandle)
+      } else if (this.allowCross) {
+        nextBounds.sort((a, b) => a - b)
+        nextHandle = nextBounds.indexOf(value)
+      }
+      this.$emit('change', {
+        handle: nextHandle,
+        bounds: nextBounds,
+      })
+      if (isFromKeyboardEvent) {
+        // known problem: because setState is async,
+        // so trigger focus will invoke handler's onEnd and another handler's onStart too early,
+        // cause onBeforeChange and onAfterChange receive wrong value.
+        // here use setState callback to hack,but not elegant
+        this.setState({}, () => {
+          this.handlesRefs[nextHandle].focus()
+        })
+      }
+    },
+    pushSurroundingHandles (bounds, handle) {
+      const value = bounds[handle]
+      let { pushable: threshold } = this
+      threshold = Number(threshold)
 
-  onMove(e, position) {
-    utils.pauseEvent(e);
-    const state = this.state;
+      let direction = 0
+      if (bounds[handle + 1] - value < threshold) {
+        direction = +1 // push to right
+      }
+      if (value - bounds[handle - 1] < threshold) {
+        direction = -1 // push to left
+      }
 
-    const value = this.calcValueByPos(position);
-    const oldValue = state.bounds[state.handle];
-    if (value === oldValue) return;
+      if (direction === 0) { return }
 
-    this.moveTo(value);
-  }
-
-  onKeyboard(e) {
-    const valueMutator = utils.getKeyboardValueMutator(e);
-
-    if (valueMutator) {
-      utils.pauseEvent(e);
-      const { state, props } = this;
-      const { bounds, handle } = state;
-      const oldValue = bounds[handle];
-      const mutatedValue = valueMutator(oldValue, props);
-      const value = this.trimAlignValue(mutatedValue);
-      if (value === oldValue) return;
-      const isFromKeyboardEvent = true;
-      this.moveTo(value, isFromKeyboardEvent);
-    }
-  }
-
-  getValue() {
-    return this.state.bounds;
-  }
-
-  getClosestBound(value) {
-    const { bounds } = this.state;
-    let closestBound = 0;
-    for (let i = 1; i < bounds.length - 1; ++i) {
-      if (value > bounds[i]) { closestBound = i; }
-    }
-    if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) {
-      closestBound = closestBound + 1;
-    }
-    return closestBound;
-  }
-
-  getBoundNeedMoving(value, closestBound) {
-    const { bounds, recent } = this.state;
-    let boundNeedMoving = closestBound;
-    const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound]);
-
-    if (isAtTheSamePoint && bounds[recent] === bounds[closestBound]) {
-      boundNeedMoving = recent;
-    }
-
-    if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) {
-      boundNeedMoving = value < bounds[closestBound + 1] ? closestBound : closestBound + 1;
-    }
-    return boundNeedMoving;
-  }
-
-  getLowerBound() {
-    return this.state.bounds[0];
-  }
-
-  getUpperBound() {
-    const { bounds } = this.state;
-    return bounds[bounds.length - 1];
-  }
-
-  /**
-   * Returns an array of possible slider points, taking into account both
-   * `marks` and `step`. The result is cached.
-   */
-  getPoints() {
-    const { marks, step, min, max } = this.props;
-    const cache = this._getPointsCache;
-    if (!cache || cache.marks !== marks || cache.step !== step) {
-      const pointsObject = { ...marks };
-      if (step !== null) {
-        for (let point = min; point <= max; point += step) {
-          pointsObject[point] = point;
+      const nextHandle = handle + direction
+      const diffToNext = direction * (bounds[nextHandle] - value)
+      if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
+        // revert to original value if pushing is impossible
+        bounds[handle] = bounds[nextHandle] - (direction * threshold)
+      }
+    },
+    pushHandle (bounds, handle, direction, amount) {
+      const originalValue = bounds[handle]
+      let currentValue = bounds[handle]
+      while (direction * (currentValue - originalValue) < amount) {
+        if (!this.pushHandleOnePoint(bounds, handle, direction)) {
+          // can't push handle enough to create the needed `amount` gap, so we
+          // revert its position to the original value
+          bounds[handle] = originalValue
+          return false
+        }
+        currentValue = bounds[handle]
+      }
+      // the handle was pushed enough to create the needed `amount` gap
+      return true
+    },
+    pushHandleOnePoint (bounds, handle, direction) {
+      const points = this.getPoints()
+      const pointIndex = points.indexOf(bounds[handle])
+      const nextPointIndex = pointIndex + direction
+      if (nextPointIndex >= points.length || nextPointIndex < 0) {
+        // reached the minimum or maximum available point, can't push anymore
+        return false
+      }
+      const nextHandle = handle + direction
+      const nextValue = points[nextPointIndex]
+      const { pushable: threshold } = this
+      const diffToNext = direction * (bounds[nextHandle] - nextValue)
+      if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
+        // couldn't push next handle, so we won't push this one either
+        return false
+      }
+      // push the handle
+      bounds[handle] = nextValue
+      return true
+    },
+    trimAlignValue (v, handle, nextProps = {}) {
+      const mergedProps = { ...this, ...nextProps }
+      const valInRange = utils.ensureValueInRange(v, mergedProps)
+      const valNotConflict = this.ensureValueNotConflict(handle, valInRange, mergedProps)
+      return utils.ensureValuePrecision(valNotConflict, mergedProps)
+    },
+    ensureValueNotConflict (handle, val, { allowCross, pushable: thershold }) {
+      const state = this.$data || {}
+      const { bounds } = state
+      handle = handle === undefined ? state.handle : handle
+      thershold = Number(thershold)
+      /* eslint-disable eqeqeq */
+      if (!allowCross && handle != null && bounds !== undefined) {
+        if (handle > 0 && val <= (bounds[handle - 1] + thershold)) {
+          return bounds[handle - 1] + thershold
+        }
+        if (handle < bounds.length - 1 && val >= (bounds[handle + 1] - thershold)) {
+          return bounds[handle + 1] - thershold
         }
       }
-      const points = Object.keys(pointsObject).map(parseFloat);
-      points.sort((a, b) => a - b);
-      this._getPointsCache = { marks, step, points };
-    }
-    return this._getPointsCache.points;
-  }
-
-  moveTo(value, isFromKeyboardEvent) {
-    const { state, props } = this;
-    const nextBounds = [...state.bounds];
-    nextBounds[state.handle] = value;
-    let nextHandle = state.handle;
-    if (props.pushable !== false) {
-      this.pushSurroundingHandles(nextBounds, nextHandle);
-    } else if (props.allowCross) {
-      nextBounds.sort((a, b) => a - b);
-      nextHandle = nextBounds.indexOf(value);
-    }
-    this.onChange({
-      handle: nextHandle,
-      bounds: nextBounds,
-    });
-    if (isFromKeyboardEvent) {
-      // known problem: because setState is async,
-      // so trigger focus will invoke handler's onEnd and another handler's onStart too early,
-      // cause onBeforeChange and onAfterChange receive wrong value.
-      // here use setState callback to hack,but not elegant
-      this.setState({}, () => {
-        this.handlesRefs[nextHandle].focus();
-      });
-    }
-  }
-
-  pushSurroundingHandles(bounds, handle) {
-    const value = bounds[handle];
-    let { pushable: threshold } = this.props;
-    threshold = Number(threshold);
-
-    let direction = 0;
-    if (bounds[handle + 1] - value < threshold) {
-      direction = +1; // push to right
-    }
-    if (value - bounds[handle - 1] < threshold) {
-      direction = -1; // push to left
-    }
-
-    if (direction === 0) { return; }
-
-    const nextHandle = handle + direction;
-    const diffToNext = direction * (bounds[nextHandle] - value);
-    if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
-      // revert to original value if pushing is impossible
-      bounds[handle] = bounds[nextHandle] - (direction * threshold);
-    }
-  }
-
-  pushHandle(bounds, handle, direction, amount) {
-    const originalValue = bounds[handle];
-    let currentValue = bounds[handle];
-    while (direction * (currentValue - originalValue) < amount) {
-      if (!this.pushHandleOnePoint(bounds, handle, direction)) {
-        // can't push handle enough to create the needed `amount` gap, so we
-        // revert its position to the original value
-        bounds[handle] = originalValue;
-        return false;
-      }
-      currentValue = bounds[handle];
-    }
-    // the handle was pushed enough to create the needed `amount` gap
-    return true;
-  }
-
-  pushHandleOnePoint(bounds, handle, direction) {
-    const points = this.getPoints();
-    const pointIndex = points.indexOf(bounds[handle]);
-    const nextPointIndex = pointIndex + direction;
-    if (nextPointIndex >= points.length || nextPointIndex < 0) {
-      // reached the minimum or maximum available point, can't push anymore
-      return false;
-    }
-    const nextHandle = handle + direction;
-    const nextValue = points[nextPointIndex];
-    const { pushable: threshold } = this.props;
-    const diffToNext = direction * (bounds[nextHandle] - nextValue);
-    if (!this.pushHandle(bounds, nextHandle, direction, threshold - diffToNext)) {
-      // couldn't push next handle, so we won't push this one either
-      return false;
-    }
-    // push the handle
-    bounds[handle] = nextValue;
-    return true;
-  }
-
-  trimAlignValue(v, handle, nextProps = {}) {
-    const mergedProps = { ...this.props, ...nextProps };
-    const valInRange = utils.ensureValueInRange(v, mergedProps);
-    const valNotConflict = this.ensureValueNotConflict(handle, valInRange, mergedProps);
-    return utils.ensureValuePrecision(valNotConflict, mergedProps);
-  }
-
-  ensureValueNotConflict(handle, val, { allowCross, pushable: thershold }) {
-    const state = this.state || {};
-    const { bounds } = state;
-    handle = handle === undefined ? state.handle : handle;
-    thershold = Number(thershold);
-    /* eslint-disable eqeqeq */
-    if (!allowCross && handle != null && bounds !== undefined) {
-      if (handle > 0 && val <= (bounds[handle - 1] + thershold)) {
-        return bounds[handle - 1] + thershold;
-      }
-      if (handle < bounds.length - 1 && val >= (bounds[handle + 1] - thershold)) {
-        return bounds[handle + 1] - thershold;
-      }
-    }
-    /* eslint-enable eqeqeq */
-    return val;
-  }
-
-  render() {
+      /* eslint-enable eqeqeq */
+      return val
+    },
+  },
+  render () {
     const {
       handle,
       bounds,
-    } = this.state;
-    const {
       prefixCls,
       vertical,
       included,
@@ -332,11 +313,11 @@ class Range extends React.Component {
       trackStyle,
       handleStyle,
       tabIndex,
-    } = this.props;
+    } = this
 
-    const offsets = bounds.map(v => this.calcOffset(v));
+    const offsets = bounds.map(v => this.calcOffset(v))
 
-    const handleClassName = `${prefixCls}-handle`;
+    const handleClassName = `${prefixCls}-handle`
     const handles = bounds.map((v, i) => handleGenerator({
       className: classNames({
         [handleClassName]: true,
@@ -354,14 +335,14 @@ class Range extends React.Component {
       disabled,
       style: handleStyle[i],
       ref: h => this.saveHandle(i, h),
-    }));
+    }))
 
     const tracks = bounds.slice(0, -1).map((_, index) => {
-      const i = index + 1;
+      const i = index + 1
       const trackClassName = classNames({
         [`${prefixCls}-track`]: true,
         [`${prefixCls}-track-${i}`]: true,
-      });
+      })
       return (
         <Track
           className={trackClassName}
@@ -372,11 +353,11 @@ class Range extends React.Component {
           style={trackStyle[index]}
           key={i}
         />
-      );
-    });
+      )
+    })
 
-    return { tracks, handles };
-  }
+    return { tracks, handles }
+  },
 }
 
-export default createSlider(Range);
+export default createSlider(Range)
diff --git a/components/vc-slider/src/Slider.jsx b/components/vc-slider/src/Slider.jsx
index 41a1094ad..de836d369 100644
--- a/components/vc-slider/src/Slider.jsx
+++ b/components/vc-slider/src/Slider.jsx
@@ -1,142 +1,142 @@
-/* eslint-disable react/prop-types */
-import React from 'react'
-import PropTypes from 'prop-types'
-import warning from 'warning'
+import PropTypes from '../../../_util/vue-types'
+import warning from '../../../_util/warning'
+import BaseMixin from '../../../_util/BaseMixin'
+import { hasProp } from '../../../_util/props-util'
 import Track from './common/Track'
 import createSlider from './common/createSlider'
 import * as utils from './utils'
 
-class Slider extends React.Component {
-  static propTypes = {
+const Slider = {
+  mixins: [BaseMixin],
+  props: {
     defaultValue: PropTypes.number,
     value: PropTypes.number,
     disabled: PropTypes.bool,
     autoFocus: PropTypes.bool,
     tabIndex: PropTypes.number,
-  };
+  },
+  data () {
+    const defaultValue = this.defaultValue !== undefined
+      ? this.defaultValue : this.min
+    const value = this.value !== undefined
+      ? this.value : defaultValue
 
-  constructor (props) {
-    super(props)
-
-    const defaultValue = props.defaultValue !== undefined
-      ? props.defaultValue : props.min
-    const value = props.value !== undefined
-      ? props.value : defaultValue
-
-    this.state = {
-      value: this.trimAlignValue(value),
-      dragging: false,
-    }
     if (process.env.NODE_ENV !== 'production') {
       warning(
-        !('minimumTrackStyle' in props),
+        !hasProp(this, 'minimumTrackStyle'),
         'minimumTrackStyle will be deprecate, please use trackStyle instead.'
       )
       warning(
-        !('maximumTrackStyle' in props),
+        !hasProp(this, 'maximumTrackStyle'),
         'maximumTrackStyle will be deprecate, please use railStyle instead.'
       )
     }
-  }
-
-  componentDidMount () {
-    const { autoFocus, disabled } = this.props
-    if (autoFocus && !disabled) {
-      this.focus()
+    return {
+      sValue: this.trimAlignValue(value),
+      dragging: false,
     }
-  }
+  },
+  mounted () {
+    this.$nextTick(() => {
+      const { autoFocus, disabled } = this
+      if (autoFocus && !disabled) {
+        this.focus()
+      }
+    })
+  },
+  watch: {
+    value: {
+      handler (val) {
+        const { min, max } = this
+        this.setChangeValue(val, min, max)
+      },
+      deep: true,
+    },
+    min (val) {
+      const { sValue, max } = this
+      this.setChangeValue(sValue, val, max)
+    },
+    max (val) {
+      const { sValue, min } = this
+      this.setChangeValue(sValue, min, val)
+    },
+  },
+  methods: {
+    setChangeValue (value, min, max) {
+      const minAmaxProps = {
+        min,
+        max,
+      }
+      const newValue = value !== undefined
+        ? value : this.sValue
+      const nextValue = this.trimAlignValue(newValue, minAmaxProps)
+      if (nextValue === this.sValue) return
 
-  componentWillReceiveProps (nextProps) {
-    if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return
+      this.setState({ sValue: nextValue })
+      if (utils.isValueOutOfRange(newValue, minAmaxProps)) {
+        this.$emit('change', nextValue)
+      }
+    },
+    onChange (state) {
+      const isNotControlled = !hasProp(this, 'value')
+      if (isNotControlled) {
+        this.setState(state)
+      }
 
-    const prevValue = this.state.value
-    const value = nextProps.value !== undefined
-      ? nextProps.value : prevValue
-    const nextValue = this.trimAlignValue(value, nextProps)
-    if (nextValue === prevValue) return
+      const changedValue = state.sValue
+      this.$emit('change', changedValue)
+    },
+    onStart (position) {
+      this.setState({ dragging: true })
+      const { sValue } = this
+      this.$emit('beforeChange', sValue)
 
-    this.setState({ value: nextValue })
-    if (utils.isValueOutOfRange(value, nextProps)) {
-      this.props.onChange(nextValue)
-    }
-  }
+      const value = this.calcValueByPos(position)
 
-  onChange (state) {
-    const props = this.props
-    const isNotControlled = !('value' in props)
-    if (isNotControlled) {
-      this.setState(state)
-    }
+      if (value === sValue) return
 
-    const changedValue = state.value
-    props.onChange(changedValue)
-  }
+      this.prevMovedHandleIndex = 0
 
-  onStart (position) {
-    this.setState({ dragging: true })
-    const props = this.props
-    const prevValue = this.getValue()
-    props.onBeforeChange(prevValue)
-
-    const value = this.calcValueByPos(position)
-    this.startValue = value
-    this.startPosition = position
-
-    if (value === prevValue) return
-
-    this.prevMovedHandleIndex = 0
-
-    this.onChange({ value })
-  }
-
-  onEnd = () => {
-    this.setState({ dragging: false })
-    this.removeDocumentEvents()
-    this.props.onAfterChange(this.getValue())
-  }
-
-  onMove (e, position) {
-    utils.pauseEvent(e)
-    const { value: oldValue } = this.state
-    const value = this.calcValueByPos(position)
-    if (value === oldValue) return
-
-    this.onChange({ value })
-  }
-
-  onKeyboard (e) {
-    const valueMutator = utils.getKeyboardValueMutator(e)
-
-    if (valueMutator) {
+      this.onChange({ sValue: value })
+    },
+    onEnd () {
+      this.setState({ dragging: false })
+      this.removeDocumentEvents()
+      this.$emit('afterChange', this.sValue)
+    },
+    onMove (e, position) {
       utils.pauseEvent(e)
-      const state = this.state
-      const oldValue = state.value
-      const mutatedValue = valueMutator(oldValue, this.props)
-      const value = this.trimAlignValue(mutatedValue)
-      if (value === oldValue) return
+      const { sValue } = this
+      const value = this.calcValueByPos(position)
+      if (value === sValue) return
 
-      this.onChange({ value })
-    }
-  }
+      this.onChange({ sValue: value })
+    },
+    onKeyboard (e) {
+      const valueMutator = utils.getKeyboardValueMutator(e)
 
-  getValue () {
-    return this.state.value
-  }
-
-  getLowerBound () {
-    return this.props.min
-  }
-
-  getUpperBound () {
-    return this.state.value
-  }
-
-  trimAlignValue (v, nextProps = {}) {
-    const mergedProps = { ...this.props, ...nextProps }
-    const val = utils.ensureValueInRange(v, mergedProps)
-    return utils.ensureValuePrecision(val, mergedProps)
-  }
+      if (valueMutator) {
+        utils.pauseEvent(e)
+        const { sValue } = this
+        const mutatedValue = valueMutator(sValue, this.$props)
+        const value = this.trimAlignValue(mutatedValue)
+        if (value === sValue) return
 
+        this.onChange({ sValue: value })
+      }
+    },
+    getLowerBound () {
+      return this.min
+    },
+    getUpperBound () {
+      return this.sValue
+    },
+    trimAlignValue (v, nextProps = {}) {
+      const mergedProps = { ...this.$props, ...nextProps }
+      const val = utils.ensureValueInRange(v, mergedProps)
+      return utils.ensureValuePrecision(val, mergedProps)
+    },
+  },
   render () {
     const {
       prefixCls,
@@ -150,7 +150,7 @@ class Slider extends React.Component {
       min,
       max,
       handle: handleGenerator,
-    } = this.props
+    } = this
     const { value, dragging } = this.state
     const offset = this.calcOffset(value)
     const handle = handleGenerator({
@@ -183,9 +183,8 @@ class Slider extends React.Component {
         }}
       />
     )
-
     return { tracks: track, handles: handle }
-  }
+  },
 }
 
 export default createSlider(Slider)
diff --git a/components/vc-slider/src/common/Marks.jsx b/components/vc-slider/src/common/Marks.jsx
index f869ca143..0adbcafdd 100644
--- a/components/vc-slider/src/common/Marks.jsx
+++ b/components/vc-slider/src/common/Marks.jsx
@@ -1,66 +1,71 @@
-import React from 'react'
 import classNames from 'classnames'
 
-const Marks = ({
-  className,
-  vertical,
-  marks,
-  included,
-  upperBound,
-  lowerBound,
-  max, min,
-  onClickLabel,
-}) => {
-  const marksKeys = Object.keys(marks)
-  const marksCount = marksKeys.length
-  const unit = marksCount > 1 ? 100 / (marksCount - 1) : 100
-  const markWidth = unit * 0.9
+const Marks = {
+  functional: true,
+  render (createElement, context) {
+    const {
+      className,
+      vertical,
+      marks,
+      included,
+      upperBound,
+      lowerBound,
+      max, min,
+    } = context.props
+    const { clickLabel } = context.listeners
+    const marksKeys = Object.keys(marks)
+    const marksCount = marksKeys.length
+    const unit = marksCount > 1 ? 100 / (marksCount - 1) : 100
+    const markWidth = unit * 0.9
 
-  const range = max - min
-  const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => {
-    const markPoint = marks[point]
-    const markPointIsObject = typeof markPoint === 'object' &&
-            !React.isValidElement(markPoint)
-    const markLabel = markPointIsObject ? markPoint.label : markPoint
-    if (!markLabel && markLabel !== 0) {
-      return null
-    }
+    const range = max - min
+    const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map(point => {
+      const markPoint = marks[point]
+      // todo
+      // const markPointIsObject = typeof markPoint === 'object' &&
+      //         !React.isValidElement(markPoint)
+      const markPointIsObject = typeof markPoint === 'object'
+      const markLabel = markPointIsObject ? markPoint.label : markPoint
+      if (!markLabel && markLabel !== 0) {
+        return null
+      }
 
-    const isActive = (!included && point === upperBound) ||
-            (included && point <= upperBound && point >= lowerBound)
-    const markClassName = classNames({
-      [`${className}-text`]: true,
-      [`${className}-text-active`]: isActive,
+      const isActive = (!included && point === upperBound) ||
+              (included && point <= upperBound && point >= lowerBound)
+      const markClassName = classNames({
+        [`${className}-text`]: true,
+        [`${className}-text-active`]: isActive,
+      })
+
+      const bottomStyle = {
+        marginBottom: '-50%',
+        bottom: `${(point - min) / range * 100}%`,
+      }
+
+      const leftStyle = {
+        width: `${markWidth}%`,
+        marginLeft: `${-markWidth / 2}%`,
+        left: `${(point - min) / range * 100}%`,
+      }
+
+      const style = vertical ? bottomStyle : leftStyle
+      const markStyle = markPointIsObject
+        ? { ...style, ...markPoint.style } : style
+      return (
+        <span
+          class={markClassName}
+          style={markStyle}
+          key={point}
+          onMouseDown={(e) => clickLabel(e, point)}
+          onTouchStart={(e) => clickLabel(e, point)}
+        >
+          {markLabel}
+        </span>
+      )
     })
 
-    const bottomStyle = {
-      marginBottom: '-50%',
-      bottom: `${(point - min) / range * 100}%`,
-    }
-
-    const leftStyle = {
-      width: `${markWidth}%`,
-      marginLeft: `${-markWidth / 2}%`,
-      left: `${(point - min) / range * 100}%`,
-    }
-
-    const style = vertical ? bottomStyle : leftStyle
-    const markStyle = markPointIsObject
-            ? { ...style, ...markPoint.style } : style
-    return (
-      <span
-        className={markClassName}
-        style={markStyle}
-        key={point}
-        onMouseDown={(e) => onClickLabel(e, point)}
-        onTouchStart={(e) => onClickLabel(e, point)}
-      >
-        {markLabel}
-      </span>
-    )
-  })
-
-  return <div className={className}>{elements}</div>
-};
+    return <div class={className}>{elements}</div>
+  },
+}
 
 export default Marks
diff --git a/components/vc-slider/src/common/Steps.jsx b/components/vc-slider/src/common/Steps.jsx
index 2859a9582..f9a734b4c 100644
--- a/components/vc-slider/src/common/Steps.jsx
+++ b/components/vc-slider/src/common/Steps.jsx
@@ -1,6 +1,5 @@
-import React from 'react'
 import classNames from 'classnames'
-import warning from 'warning'
+import warning from '../../../_util/warning'
 
 const calcPoints = (vertical, marks, dots, step, min, max) => {
   warning(
@@ -17,28 +16,32 @@ const calcPoints = (vertical, marks, dots, step, min, max) => {
   return points
 }
 
-const Steps = ({ prefixCls, vertical, marks, dots, step, included,
-  lowerBound, upperBound, max, min, dotStyle, activeDotStyle }) => {
-  const range = max - min
-  const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => {
-    const offset = `${Math.abs(point - min) / range * 100}%`
+const Steps = {
+  functional: true,
+  render (createElement, context) {
+    const { prefixCls, vertical, marks, dots, step, included,
+      lowerBound, upperBound, max, min, dotStyle, activeDotStyle } = context.data
+    const range = max - min
+    const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => {
+      const offset = `${Math.abs(point - min) / range * 100}%`
 
-    const isActived = (!included && point === upperBound) ||
-            (included && point <= upperBound && point >= lowerBound)
-    let style = vertical ? { bottom: offset, ...dotStyle } : { left: offset, ...dotStyle }
-    if (isActived) {
-      style = { ...style, ...activeDotStyle }
-    }
+      const isActived = (!included && point === upperBound) ||
+              (included && point <= upperBound && point >= lowerBound)
+      let style = vertical ? { bottom: offset, ...dotStyle } : { left: offset, ...dotStyle }
+      if (isActived) {
+        style = { ...style, ...activeDotStyle }
+      }
 
-    const pointClassName = classNames({
-      [`${prefixCls}-dot`]: true,
-      [`${prefixCls}-dot-active`]: isActived,
+      const pointClassName = classNames({
+        [`${prefixCls}-dot`]: true,
+        [`${prefixCls}-dot-active`]: isActived,
+      })
+
+      return <span className={pointClassName} style={style} key={point} />
     })
 
-    return <span className={pointClassName} style={style} key={point} />
-  })
-
-  return <div className={`${prefixCls}-step`}>{elements}</div>
+    return <div className={`${prefixCls}-step`}>{elements}</div>
+  },
 }
 
 export default Steps
diff --git a/components/vc-slider/src/common/createSlider.jsx b/components/vc-slider/src/common/createSlider.jsx
index 9ef8ddcbe..1fdbd0256 100644
--- a/components/vc-slider/src/common/createSlider.jsx
+++ b/components/vc-slider/src/common/createSlider.jsx
@@ -1,8 +1,8 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import addEventListener from 'rc-util/lib/Dom/addEventListener'
 import classNames from 'classnames'
-import warning from 'warning'
+import PropTypes from '../../../_util/vue-types'
+import addEventListener from '../../../_util/Dom/addEventListener'
+import warning from '../../../_util/warning'
+import { initDefaultProps } from '../../../_util/props-util'
 import Steps from './Steps'
 import Marks from './Marks'
 import Handle from '../Handle'
@@ -11,42 +11,32 @@ import * as utils from '../utils'
 function noop () {}
 
 export default function createSlider (Component) {
-  return class ComponentEnhancer extends Component {
-    static displayName = `ComponentEnhancer(${Component.displayName})`;
-    static propTypes = {
-      ...Component.propTypes,
-      min: PropTypes.number,
-      max: PropTypes.number,
-      step: PropTypes.number,
-      marks: PropTypes.object,
-      included: PropTypes.bool,
-      className: PropTypes.string,
-      prefixCls: PropTypes.string,
-      disabled: PropTypes.bool,
-      children: PropTypes.any,
-      onBeforeChange: PropTypes.func,
-      onChange: PropTypes.func,
-      onAfterChange: PropTypes.func,
-      handle: PropTypes.func,
-      dots: PropTypes.bool,
-      vertical: PropTypes.bool,
-      style: PropTypes.object,
-      minimumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate
-      maximumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate
-      handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
-      trackStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
-      railStyle: PropTypes.object,
-      dotStyle: PropTypes.object,
-      activeDotStyle: PropTypes.object,
-      autoFocus: PropTypes.bool,
-      onFocus: PropTypes.func,
-      onBlur: PropTypes.func,
-    };
-
-    static defaultProps = {
+  // const displayName = `ComponentEnhancer(${Component.displayName})`
+  const propTypes = {
+    ...Component.propTypes,
+    min: PropTypes.number,
+    max: PropTypes.number,
+    step: PropTypes.number,
+    marks: PropTypes.object,
+    included: PropTypes.bool,
+    prefixCls: PropTypes.string,
+    disabled: PropTypes.bool,
+    handle: PropTypes.func,
+    dots: PropTypes.bool,
+    vertical: PropTypes.bool,
+    minimumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate
+    maximumTrackStyle: PropTypes.object, // just for compatibility, will be deperecate
+    handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
+    trackStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
+    railStyle: PropTypes.object,
+    dotStyle: PropTypes.object,
+    activeDotStyle: PropTypes.object,
+    autoFocus: PropTypes.bool,
+  }
+  return {
+    props: initDefaultProps(propTypes, {
       ...Component.defaultProps,
       prefixCls: 'rc-slider',
-      className: '',
       min: 0,
       max: 100,
       step: 1,
@@ -55,9 +45,6 @@ export default function createSlider (Component) {
         delete restProps.dragging
         return <Handle {...restProps} key={index} />
       },
-      onBeforeChange: noop,
-      onChange: noop,
-      onAfterChange: noop,
       included: true,
       disabled: false,
       dots: false,
@@ -67,13 +54,10 @@ export default function createSlider (Component) {
       railStyle: {},
       dotStyle: {},
       activeDotStyle: {},
-    };
-
-    constructor (props) {
-      super(props)
-
+    }),
+    data () {
       if (process.env.NODE_ENV !== 'production') {
-        const { step, max, min } = props
+        const { step, max, min } = this
         warning(
           step && Math.floor(step) === step ? (max - min) % step === 0 : true,
           'Slider[max] - Slider[min] (%s) should be a multiple of Slider[step] (%s)',
@@ -82,190 +66,163 @@ export default function createSlider (Component) {
         )
       }
       this.handlesRefs = {}
-    }
-
-    componentWillUnmount () {
-      if (super.componentWillUnmount) super.componentWillUnmount()
-      this.removeDocumentEvents()
-    }
-
-    componentDidMount () {
-      // Snapshot testing cannot handle refs, so be sure to null-check this.
-      this.document = this.sliderRef && this.sliderRef.ownerDocument
-    }
-
-    onMouseDown = (e) => {
-      if (e.button !== 0) { return }
-
-      const isVertical = this.props.vertical
-      let position = utils.getMousePosition(isVertical, e)
-      if (!utils.isEventFromHandle(e, this.handlesRefs)) {
-        this.dragOffset = 0
-      } else {
-        const handlePosition = utils.getHandleCenterPosition(isVertical, e.target)
-        this.dragOffset = position - handlePosition
-        position = handlePosition
-      }
-      this.removeDocumentEvents()
-      this.onStart(position)
-      this.addDocumentMouseEvents()
-    }
-
-    onTouchStart = (e) => {
-      if (utils.isNotTouchEvent(e)) return
-
-      const isVertical = this.props.vertical
-      let position = utils.getTouchPosition(isVertical, e)
-      if (!utils.isEventFromHandle(e, this.handlesRefs)) {
-        this.dragOffset = 0
-      } else {
-        const handlePosition = utils.getHandleCenterPosition(isVertical, e.target)
-        this.dragOffset = position - handlePosition
-        position = handlePosition
-      }
-      this.onStart(position)
-      this.addDocumentTouchEvents()
-      utils.pauseEvent(e)
-    }
-
-    onFocus = (e) => {
-      const { onFocus, vertical } = this.props
-      if (utils.isEventFromHandle(e, this.handlesRefs)) {
-        const handlePosition = utils.getHandleCenterPosition(vertical, e.target)
-        this.dragOffset = 0
-        this.onStart(handlePosition)
-        utils.pauseEvent(e)
-        if (onFocus) {
-          onFocus(e)
+      return {}
+    },
+    beforeDestroy () {
+      this.$nextTick(() => {
+        // if (super.componentWillUnmount) super.componentWillUnmount()
+        this.removeDocumentEvents()
+      })
+    },
+    mounted () {
+      this.$nextTick(() => {
+        // Snapshot testing cannot handle refs, so be sure to null-check this.
+        this.document = this.$refs.sliderRef && this.$refs.sliderRef.ownerDocument
+      })
+    },
+    methods: {
+      onMouseDown (e) {
+        if (e.button !== 0) { return }
+        const isVertical = this.vertical
+        let position = utils.getMousePosition(isVertical, e)
+        if (!utils.isEventFromHandle(e, this.handlesRefs)) {
+          this.dragOffset = 0
+        } else {
+          const handlePosition = utils.getHandleCenterPosition(isVertical, e.target)
+          this.dragOffset = position - handlePosition
+          position = handlePosition
         }
-      }
-    }
+        this.removeDocumentEvents()
+        this.onStart(position)
+        this.addDocumentMouseEvents()
+      },
+      onTouchStart (e) {
+        if (utils.isNotTouchEvent(e)) return
 
-    onBlur = (e) => {
-      const { onBlur } = this.props
-      this.onEnd(e)
-      if (onBlur) {
-        onBlur(e)
-      }
-    };
+        const isVertical = this.vertical
+        let position = utils.getTouchPosition(isVertical, e)
+        if (!utils.isEventFromHandle(e, this.handlesRefs)) {
+          this.dragOffset = 0
+        } else {
+          const handlePosition = utils.getHandleCenterPosition(isVertical, e.target)
+          this.dragOffset = position - handlePosition
+          position = handlePosition
+        }
+        this.onStart(position)
+        this.addDocumentTouchEvents()
+        utils.pauseEvent(e)
+      },
+      onFocus (e) {
+        const { vertical } = this
+        if (utils.isEventFromHandle(e, this.handlesRefs)) {
+          const handlePosition = utils.getHandleCenterPosition(vertical, e.target)
+          this.dragOffset = 0
+          this.onStart(handlePosition)
+          utils.pauseEvent(e)
+          this.$emit('focus', e)
+        }
+      },
+      onBlur (e) {
+        this.onEnd(e)
+        this.$emit('blur', e)
+      },
+      addDocumentTouchEvents () {
+        // just work for Chrome iOS Safari and Android Browser
+        this.onTouchMoveListener = addEventListener(this.document, 'touchmove', this.onTouchMove)
+        this.onTouchUpListener = addEventListener(this.document, 'touchend', this.onEnd)
+      },
+      addDocumentMouseEvents () {
+        this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove)
+        this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd)
+      },
+      removeDocumentEvents () {
+        /* eslint-disable no-unused-expressions */
+        this.onTouchMoveListener && this.onTouchMoveListener.remove()
+        this.onTouchUpListener && this.onTouchUpListener.remove()
 
-    addDocumentTouchEvents () {
-      // just work for Chrome iOS Safari and Android Browser
-      this.onTouchMoveListener = addEventListener(this.document, 'touchmove', this.onTouchMove)
-      this.onTouchUpListener = addEventListener(this.document, 'touchend', this.onEnd)
-    }
+        this.onMouseMoveListener && this.onMouseMoveListener.remove()
+        this.onMouseUpListener && this.onMouseUpListener.remove()
+        /* eslint-enable no-unused-expressions */
+      },
+      onMouseUp () {
+        if (this.handlesRefs[this.prevMovedHandleIndex]) {
+          this.handlesRefs[this.prevMovedHandleIndex].clickFocus()
+        }
+      },
+      onMouseMove (e) {
+        if (!this.$refs.sliderRef) {
+          this.onEnd()
+          return
+        }
+        const position = utils.getMousePosition(this.vertical, e)
+        this.onMove(e, position - this.dragOffset)
+      },
+      onTouchMove (e) {
+        if (utils.isNotTouchEvent(e) || !this.$refs.sliderRef) {
+          this.onEnd()
+          return
+        }
 
-    addDocumentMouseEvents () {
-      this.onMouseMoveListener = addEventListener(this.document, 'mousemove', this.onMouseMove)
-      this.onMouseUpListener = addEventListener(this.document, 'mouseup', this.onEnd)
-    }
+        const position = utils.getTouchPosition(this.vertical, e)
+        this.onMove(e, position - this.dragOffset)
+      },
+      onKeyDown (e) {
+        if (this.$refs.sliderRef && utils.isEventFromHandle(e, this.handlesRefs)) {
+          this.onKeyboard(e)
+        }
+      },
+      focus () {
+        if (!this.disabled) {
+          this.handlesRefs[0].focus()
+        }
+      },
+      blur () {
+        if (!this.disabled) {
+          this.handlesRefs[0].blur()
+        }
+      },
+      getSliderStart () {
+        const slider = this.$refs.sliderRef
+        const rect = slider.getBoundingClientRect()
 
-    removeDocumentEvents () {
-      /* eslint-disable no-unused-expressions */
-      this.onTouchMoveListener && this.onTouchMoveListener.remove()
-      this.onTouchUpListener && this.onTouchUpListener.remove()
-
-      this.onMouseMoveListener && this.onMouseMoveListener.remove()
-      this.onMouseUpListener && this.onMouseUpListener.remove()
-      /* eslint-enable no-unused-expressions */
-    }
-
-    onMouseUp = () => {
-      if (this.handlesRefs[this.prevMovedHandleIndex]) {
-        this.handlesRefs[this.prevMovedHandleIndex].clickFocus()
-      }
-    }
-
-    onMouseMove = (e) => {
-      if (!this.sliderRef) {
-        this.onEnd()
-        return;
-      }
-      const position = utils.getMousePosition(this.props.vertical, e)
-      this.onMove(e, position - this.dragOffset)
-    }
-
-    onTouchMove = (e) => {
-      if (utils.isNotTouchEvent(e) || !this.sliderRef) {
-        this.onEnd()
-        return;
-      }
-
-      const position = utils.getTouchPosition(this.props.vertical, e)
-      this.onMove(e, position - this.dragOffset)
-    }
-
-    onKeyDown = (e) => {
-      if (this.sliderRef && utils.isEventFromHandle(e, this.handlesRefs)) {
-        this.onKeyboard(e)
-      }
-    }
-
-    focus () {
-      if (!this.props.disabled) {
-        this.handlesRefs[0].focus()
-      }
-    }
-
-    blur () {
-      if (!this.props.disabled) {
-        this.handlesRefs[0].blur()
-      }
-    }
-
-    getSliderStart () {
-      const slider = this.sliderRef
-      const rect = slider.getBoundingClientRect()
-
-      return this.props.vertical ? rect.top : rect.left
-    }
-
-    getSliderLength () {
-      const slider = this.sliderRef
-      if (!slider) {
-        return 0
-      }
-
-      const coords = slider.getBoundingClientRect()
-      return this.props.vertical ? coords.height : coords.width
-    }
-
-    calcValue (offset) {
-      const { vertical, min, max } = this.props
-      const ratio = Math.abs(Math.max(offset, 0) / this.getSliderLength())
-      const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min
-      return value
-    }
-
-    calcValueByPos (position) {
-      const pixelOffset = position - this.getSliderStart()
-      const nextValue = this.trimAlignValue(this.calcValue(pixelOffset))
-      return nextValue
-    }
-
-    calcOffset (value) {
-      const { min, max } = this.props
-      const ratio = (value - min) / (max - min)
-      return ratio * 100
-    }
-
-    saveSlider = (slider) => {
-      this.sliderRef = slider
-    }
-
-    saveHandle (index, handle) {
-      this.handlesRefs[index] = handle
-    }
-
-    onClickMarkLabel = (e, value) => {
-      e.stopPropagation()
-      this.onChange({ value })
-    }
+        return this.vertical ? rect.top : rect.left
+      },
+      getSliderLength () {
+        const slider = this.$refs.sliderRef
+        if (!slider) {
+          return 0
+        }
 
+        const coords = slider.getBoundingClientRect()
+        return this.vertical ? coords.height : coords.width
+      },
+      calcValue (offset) {
+        const { vertical, min, max } = this
+        const ratio = Math.abs(Math.max(offset, 0) / this.getSliderLength())
+        const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min
+        return value
+      },
+      calcValueByPos (position) {
+        const pixelOffset = position - this.getSliderStart()
+        const nextValue = this.trimAlignValue(this.calcValue(pixelOffset))
+        return nextValue
+      },
+      calcOffset (value) {
+        const { min, max } = this.props
+        const ratio = (value - min) / (max - min)
+        return ratio * 100
+      },
+      saveHandle (index, handle) {
+        this.handlesRefs[index] = handle
+      },
+      onClickMarkLabel (e, value) {
+        e.stopPropagation()
+        this.$emit('change', { value })
+      },
+    },
     render () {
       const {
         prefixCls,
-        className,
         marks,
         dots,
         step,
@@ -274,35 +231,46 @@ export default function createSlider (Component) {
         vertical,
         min,
         max,
-        children,
         maximumTrackStyle,
-        style,
         railStyle,
         dotStyle,
         activeDotStyle,
-      } = this.props
+      } = this
       const { tracks, handles } = super.render()
 
       const sliderClassName = classNames(prefixCls, {
         [`${prefixCls}-with-marks`]: Object.keys(marks).length,
         [`${prefixCls}-disabled`]: disabled,
         [`${prefixCls}-vertical`]: vertical,
-        [className]: className,
       })
+      const markProps = {
+        props: {
+          vertical,
+          marks,
+          included,
+          lowerBound: this.getLowerBound(),
+          upperBound: this.getUpperBound(),
+          max,
+          min,
+          className: `${prefixCls}-mark`,
+        },
+        on: {
+          clickLabel: disabled ? noop : this.onClickMarkLabel,
+        },
+      }
       return (
         <div
-          ref={this.saveSlider}
-          className={sliderClassName}
+          ref='sliderRef'
+          class={sliderClassName}
           onTouchStart={disabled ? noop : this.onTouchStart}
           onMouseDown={disabled ? noop : this.onMouseDown}
           onMouseUp={disabled ? noop : this.onMouseUp}
           onKeyDown={disabled ? noop : this.onKeyDown}
           onFocus={disabled ? noop : this.onFocus}
           onBlur={disabled ? noop : this.onBlur}
-          style={style}
         >
           <div
-            className={`${prefixCls}-rail`}
+            class={`${prefixCls}-rail`}
             style={{
               ...maximumTrackStyle,
               ...railStyle,
@@ -325,19 +293,11 @@ export default function createSlider (Component) {
           />
           {handles}
           <Marks
-            className={`${prefixCls}-mark`}
-            onClickLabel={disabled ? noop : this.onClickMarkLabel}
-            vertical={vertical}
-            marks={marks}
-            included={included}
-            lowerBound={this.getLowerBound()}
-            upperBound={this.getUpperBound()}
-            max={max}
-            min={min}
+            {...markProps}
           />
-          {children}
+          {this.$slots.default}
         </div>
       )
-    }
+    },
   }
 }
diff --git a/components/vc-slider/src/createSliderWithTooltip.jsx b/components/vc-slider/src/createSliderWithTooltip.jsx
index 002357a48..23441d6be 100644
--- a/components/vc-slider/src/createSliderWithTooltip.jsx
+++ b/components/vc-slider/src/createSliderWithTooltip.jsx
@@ -1,80 +1,91 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Tooltip from 'rc-tooltip';
-import Handle from './Handle';
+import PropTypes from '../../../_util/vue-types'
+import BaseMixin from '../../../_util/BaseMixin'
+import Tooltip from '../../vc-tooltip'
+import Handle from './Handle'
 
-export default function createSliderWithTooltip(Component) {
-  return class ComponentWrapper extends React.Component {
-    static propTypes = {
-      tipFormatter: PropTypes.func,
-      handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]),
-      tipProps: PropTypes.object,
-    };
-    static defaultProps = {
-      tipFormatter(value) { return value; },
-      handleStyle: [{}],
-      tipProps: {},
-    };
-    constructor(props) {
-      super(props);
-      this.state = { visibles: {} };
-    }
-    handleTooltipVisibleChange = (index, visible) => {
-      this.setState((prevState) => {
-        return {
-          visibles: {
-            ...prevState.visibles,
-            [index]: visible,
-          },
-        };
-      });
-    }
-    handleWithTooltip = ({ value, dragging, index, disabled, ...restProps }) => {
-      const {
-        tipFormatter,
-        tipProps,
-        handleStyle,
-      } = this.props;
-
-      const {
-        prefixCls = 'rc-slider-tooltip',
-        overlay = tipFormatter(value),
-        placement = 'top',
-        visible = visible || false,
-        ...restTooltipProps,
-      } = tipProps;
-
-      let handleStyleWithIndex;
-      if (Array.isArray(handleStyle)) {
-        handleStyleWithIndex = handleStyle[index] || handleStyle[0];
-      } else {
-        handleStyleWithIndex = handleStyle;
+export default function createSliderWithTooltip (Component) {
+  return {
+    mixins: [BaseMixin],
+    props: {
+      tipFormatter: PropTypes.func.def((value) => { return value }),
+      handleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.arrayOf(PropTypes.object)]).def([{}]),
+      tipProps: PropTypes.object.def({}),
+    },
+    data () {
+      return {
+        visibles: {},
       }
+    },
+    methods: {
+      handleTooltipVisibleChange (index, visible) {
+        this.setState((prevState) => {
+          return {
+            visibles: {
+              ...prevState.visibles,
+              [index]: visible,
+            },
+          }
+        })
+      },
+      handleWithTooltip ({ value, dragging, index, disabled, ...restProps }) {
+        const {
+          tipFormatter,
+          tipProps,
+          handleStyle,
+        } = this.$props
 
-      return (
-        <Tooltip
-          {...restTooltipProps}
-          prefixCls={prefixCls}
-          overlay={overlay}
-          placement={placement}
-          visible={(!disabled && (this.state.visibles[index] || dragging)) || visible}
-          key={index}
-        >
+        const {
+          prefixCls = 'rc-slider-tooltip',
+          overlay = tipFormatter(value),
+          placement = 'top',
+          visible = visible || false,
+          ...restTooltipProps } = tipProps
 
-          <Handle
-            {...restProps}
-            style={{
-              ...handleStyleWithIndex,
-            }}
-            value={value}
-            onMouseEnter={() => this.handleTooltipVisibleChange(index, true)}
-            onMouseLeave={() => this.handleTooltipVisibleChange(index, false)}
-          />
-        </Tooltip>
-      );
-    }
-    render() {
-      return <Component {...this.props} handle={this.handleWithTooltip} />;
-    }
-  };
+        let handleStyleWithIndex
+        if (Array.isArray(handleStyle)) {
+          handleStyleWithIndex = handleStyle[index] || handleStyle[0]
+        } else {
+          handleStyleWithIndex = handleStyle
+        }
+
+        const tooltipProps = {
+          props: {
+            prefixCls,
+            overlay,
+            placement,
+            visible: (!disabled && (this.visibles[index] || dragging)) || visible,
+            ...restTooltipProps,
+          },
+          key: index,
+        }
+        const handleProps = {
+          props: {
+            value,
+            ...restProps,
+          },
+          on: {
+            mouseenter: () => this.handleTooltipVisibleChange(index, true),
+            mouseleave: () => this.handleTooltipVisibleChange(index, false),
+          },
+          style: {
+            ...handleStyleWithIndex,
+          },
+        }
+
+        return (
+          <Tooltip
+            {...tooltipProps}
+          >
+
+            <Handle
+              {...handleProps}
+            />
+          </Tooltip>
+        )
+      },
+    },
+    render () {
+      return <Component {...this.$props} handle={this.handleWithTooltip} />
+    },
+  }
 }
diff --git a/components/vc-slider/src/utils.js b/components/vc-slider/src/utils.js
index 1b2f4a036..170f2ccf1 100644
--- a/components/vc-slider/src/utils.js
+++ b/components/vc-slider/src/utils.js
@@ -1,9 +1,8 @@
-import { findDOMNode } from 'react-dom'
 import keyCode from '../../_util/KeyCode'
 
 export function isEventFromHandle (e, handles) {
   return Object.keys(handles)
-    .some(key => e.target === findDOMNode(handles[key]))
+    .some(key => e.target === handles[key])
 }
 
 export function isValueOutOfRange (value, { min, max }) {