React —— ref使用分析

ref分析

为什么有ref,虽然官方总是不推荐使用这种破坏整体框架的api,但是实际开发,总有一些场景需要直接操作DOM元素,所以有了这个api.但是如果能不使用尽量不使用.

  • 破坏了"属性和状态去映射视图",正常流程中的组件属性均有数据映射而来,绑定了ref相当于提供直接修改属性的额外途径,导致属性不可控.
  • 破坏了"属性不可变性,单向数据流",增加额外了操作数据的途径,可能改变属性不可变性,让数据的流动不可控.
  • 降低了可读性,破坏了整体代码风格和组织结构.

虽然,有种种不利,但是在一些场景确实有效并且真香~

ref使用场景

  • 绘图

    1. 通过canvas元素获取画板上下文
    2. 通过canvas元素获取父元素宽度和高度,自适应自身高度和宽度
    3. 监听父元素resize,更新视图。
    class DrawPanel extends React.Component {
        constructor(props) {
            super(props)
            this.state = {
                value: 1
            }
        }
        handleChange = (e) => {
            this.setState({
                value: e.target.value
            })
        }
        render () {
            return (
                
    {this.state.value}
    ) } } class DrawRing extends React.Component { canvas = React.createRef() constructor(props) { super(props); } componentDidMount () { this.currentValue = 0; this.ctx = this.canvas.current.getContext('2d'); this.clear(); this.draw(); this.canvas.current.parentNode.addEventListener('resize', () => { this.clear(); this.draw(); }) } componentDidUpdate () { this.clear(); this.draw(); } clear () { const canvas = this.canvas.current; const parentNode = canvas.parentNode; canvas.width = parentNode.offsetWidth; canvas.height = parentNode.offsetHeight; this.centerPos = [canvas.width / 2, canvas.height / 2]; } draw () { let { value = 10, color = 'red', duration = 1000, bgColor = '#e3e3e3', wd = 0 } = this.props, ctx = this.ctx, centerPos = this.centerPos, r = 1.5 * Math.min.apply(null, centerPos) / 2, currentValue = this.currentValue, speed = 3.6 * 10 * (value - currentValue) / duration; speed = Math[speed >0 ? 'max': 'min'](speed > 0 ? 0.0001 : -0.0001, speed); wd = wd || r / 5; currentValue += speed; if (speed > 0 && currentValue + speed > value) { currentValue = value } if (speed < 0 && currentValue + speed < value) { currentValue = value } ctx.beginPath(); ctx.arc(centerPos[0], centerPos[1], r, 0, Math.PI * 2 * (currentValue / 100), false); ctx.strokeStyle = color; ctx.lineWidth = wd; ctx.stroke() ctx.closePath(); ctx.beginPath(); ctx.arc(centerPos[0], centerPos[1], r, Math.PI * 2 * (currentValue / 100), Math.PI * 2, false); ctx.strokeStyle = bgColor; ctx.lineWidth = wd; ctx.stroke() ctx.closePath(); ctx.beginPath(); ctx.font = "normal normal normal " + r / 4 + "px arial"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = color; ctx.fillText((Math.ceil(currentValue*100)/100).toFixed(2) + "%", centerPos[0], centerPos[1]) ctx.closePath(); this.currentValue = Number(currentValue); clearTimeout(this.timer); if (currentValue != value) { this.timer = setTimeout(() => { this.clear(); this.draw(); }) } } render () { return
    Your browser does not support the canvas element.
    } } ReactDOM.render(, document.querySelector('#root-canvas'))

    ps:写这个demo过程遇到一个问题,父组件中state变化,触发子组件的props变化,到底应该在什么周期中进行操作(留到后面生命周期章节再详细分析)。

  • 购物车动画

    1. 通过ref获取购物车组件,父组件调用子组件的方法(goods=》父组件=》cart组件),创建小球并开始动画。
    2. 通过ref获取购物车的位置,即小球的落点
    3. 通过ref获取小球包裹元素。通过监听过渡结束事件移除元素
    4. cart组件中直接通过React.createRef创建ref并绑定,父组件获取子组件通过传递一个函数onRef,子组件在建立的时候通过调用此函数传递自己this,绑定到父组件的属性上。
    
    
    
    class Shop extends React.Component {
        state = {
            goods: [
                {
                    name: "苹果",
                    count: 0
                },
                {
                    name: "香蕉",
                    count: 1
                },
                {
                    name: "樱桃",
                    count: 0
                }
            ],
        }
        buy = (value, index, e) => {
            let newGoods = this.state.goods.map(function (good) {
                if (value.name == good.name) good.count += 1
                return good
            })
            this.setState({
                goods: newGoods
            })
            e.persist()
            this.cartRef.add({
                left: e.nativeEvent.x,
                top: e.nativeEvent.y,
            })
        }
        onRef = ref => {
            this.cartRef = ref
        }
        del = (value, index) => {
            let newGoods = this.state.goods.map(function (good) {
                if (value.name == good.name) good.count -= 1
                return good
            })
            this.setState({
                goods: newGoods
            })
        }
    
        render () {
            const goods = this.state.goods,
                buyGoods = goods.filter(value => {
                    return value.count != 0
                })
            return 
    } } class Goods extends React.Component { buy = (value, index, e) => { this.props.buy(value, index, e) } render () { const data = this.props.data; return
      { data.map((value, index) => (
    • { this.buy(value, index, e) }} key={index}>{value.name}{value.count}
    • )) }
    } } class Cart extends React.Component { refWrap = React.createRef() cartRef = React.createRef() constructor(props) { super(props) this.props.onRef(this); } del = (value, index) => { this.props.del(value, index) } state = { balls: [] } ballId = 0 add = (ball) => { ball = { style: ball, ballId: this.ballId++ } this.setState({ balls: [ ball, ...this.state.balls ] }) setTimeout(() => { this.animated(); }, 0) } del = (index) => { if (!index) index = 0; var newBalls = this.state.balls.map(value => value) newBalls.pop(); this.setState({ balls: newBalls }) } componentDidMount () { const rect = this.cartRef.current.getBoundingClientRect() this.target.left = rect.x + rect.width / 2; this.target.top = rect.y + rect.height / 2; this.refWrap.current.addEventListener('webkitTransitionEnd', (e) => { this.del() }) } target = { left: 0, top: 0 } animated = () => { if (this.state.balls.length > 0 && this.state.balls.some(value => value.left != 0)) { var newBalls = this.state.balls.map(value => { return Object.assign({}, { style: this.target }, { ballId: value.ballId }) }); this.setState({ balls: newBalls }) console.log(this.state) } } render () { const data = this.props.data; const balls = this.state.balls; return
      { balls.map((ball, index) =>
    • ) } { data.map((value, index) => (
    • { this.del(value, index) }} key={index}>{value.name}{value.count}
    • )) }
    } } ReactDOM.render(, document.querySelector('#root-cart'))
  • 越级绑定ref

React.forwardRef:首先为什么要使用这个方法,因为React中不支持想传递普通属性props一样传递ref这个命名的属性。


如上,在Test组件中testname可以通过this.props.testname获取到父组件传递的值(传入子组件的参数)。但是ref不同,因为ref的作用是绑定组件或者DOM,可以通过绑定值操作组件或者DOM,所以ref的值是它绑定的组件或者DOM,并不是传入到组件内,所以并不能使用props属性获取。那么如果我们要传递一个ref进入子组件怎么做呢?那么就不能使用ref,可以换个名字:

class App extends React.Component {
    sonRef = React.createRef()
    grandsonRef = React.createRef()
    onClickHandle=()=>{
        console.log(this.sonRef)
        console.log(this.grandsonRef)
    }
    render () {
        return 
} } class Son extends React.Component { render () { const {diyref} = this.props; return
Son
} } class GrandSon extends React.Component { render () { console.log(this.props) return
GrandSon
} } ReactDOM.render(, document.querySelector('#root'))

如上,我们通过diyref传递一个父组件定义的ref到子组件中,然后通过子组件绑定到孙组件上。同理,我们也可以采用传递函数的方式传递一个函数子组件,然后子组件把这个函数传递到孙组件,在孙组件中调用这个函数返回自己,最终绑定到父组件的属性上。
那么除了换名称,还有其他什么方式传递ref吗,事实上有的,官方还提供了一个React.forwardRef来做这件事。

class App extends React.Component {
    sonRef = React.createRef()
    grandsonRef = React.createRef()
    onClickHandle=()=>{
        console.log(this.sonRef)
        console.log(this.grandsonRef)
    }
    render () {
        return 
} }

正常情况下,上面的ref直接绑定了Son,所以在Son中我们获取不到ref,但是如果我希望在Son中能获取ref传递的参数,而不是把当做绑定操作,我们可以是用React.forwardRef来定义Son:

const Son = React.forwardRef((props,ref)=>{
    return  
Son
})

这种情况下,ref当做第二参数直接传入了,而不是绑定在组件上了。

看到这里,官网上的例子就不难理解了,结合上面,我们有更好的实践:

function logProps (Component) {
    class LogProps extends React.Component {
        componentDidUpdate (prevProps) {
            console.log('old props:', prevProps);
            console.log('new props:', this.props);
        }

        render () {
            const { forwardedRef, ...rest } = this.props;

            // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
            return ;
        }
    }
    return React.forwardRef((props, ref) => {
        return ;
    });
}

上面调用logProps时候加得ref被挂载到被 LogProps 包裹的子组件上,同时利用常规 prop 属性传递ref和React.forwardRef,而我们只需要正常的编写实际的LogProps组件即可。

你可能感兴趣的:(个人学习,React,react,js)