V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
abcbuzhiming
V2EX  ›  JavaScript

在 Vue2 里如何实现“一边计算,一边更新页面元素”这样的功能

  •  
  •   abcbuzhiming · 2017-04-07 10:38:56 +08:00 · 7317 次点击
    这是一个创建于 2791 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我估计应该是我想的方向不太对,放狗了好久也没找到答案。实际效果并不复杂,页面上有一个按钮和一个简单的数字 v-text ,点击按钮,按钮绑定的方法会把一个变量累加 n 次,要求每累加一次,页面就更新一次并显示出来,然而实际操作发现不行,按钮绑定的 method 会把 n 一次累加完,然后页面才会刷新显示出来,因此我看不到累加过程,只能看到最终结果,最开始发现 Vue2 有一个$forceUpdate()方法能强制刷新,但是实际使用毫无效果
    35 条回复    2017-04-07 21:11:45 +08:00
    loy6491
        1
    loy6491  
       2017-04-07 10:46:20 +08:00
    既然想看到过程,就加上 setTimeout 啊
    445141126
        2
    445141126  
       2017-04-07 10:54:32 +08:00
    abcbuzhiming
        3
    abcbuzhiming  
    OP
       2017-04-07 11:22:30 +08:00
    @loy6491 你是对的,也就是说如果我想看到过程,就必须设置成异步任务,让 Vue 的处理 method 函数结束才行
    abcbuzhiming
        4
    abcbuzhiming  
    OP
       2017-04-07 11:23:09 +08:00
    @445141126 谢谢,但是这个不是我要的,我并不是需要一个过渡效果,而是需要计算的过程
    wly19960911
        5
    wly19960911  
       2017-04-07 11:29:25 +08:00
    那为什么要交给 VUE 去做呢, vue 的计算属性是类似于表达式公式的,用自己的方法异步完成不好么,构造自动机自动执行输出结果如何?

    我认为这种东西不要依赖框架。
    ferrum
        6
    ferrum  
       2017-04-07 11:33:29 +08:00
    这不就是双向数据绑定吗,为什么直接更改会无效?楼主可以把示例发出来看看。
    coolzjy
        7
    coolzjy  
       2017-04-07 11:39:17 +08:00 via iPhone
    Batch Update 机制,可通过异步操作解决
    fszaer
        8
    fszaer  
       2017-04-07 11:44:49 +08:00
    @ferrum
    大概是这个意思吧
    如果在方法中
    const setNumber=()=>{
    data.s=1;
    data.s=2;
    data.s=3;
    }
    方法执行后绑定的 s 属性显示的是 3 ,而 po 希望 显示出 1,2,3 这样?
    abcbuzhiming
        9
    abcbuzhiming  
    OP
       2017-04-07 11:47:55 +08:00
    @wly19960911 我估计可能是需要异步,我以前以为可以在 Vue 的 method 定义的函数中同步实现。
    abcbuzhiming
        10
    abcbuzhiming  
    OP
       2017-04-07 11:50:35 +08:00
    @ferrum 代码如下,我估计我个人还是没理解 Vue 的思路造成的,或者 Vue 并不允许在定义的函数结束前进行 re-render

    <div id="group-send-sms">

    <span v-text="bianliang"></span>
    <input v-on:click="test" type="button" value="测试" />



    </div>

    <script type="text/javascript">
    var vm = new Vue({
    el: "#group-send-sms",
    data: {

    bianliang:0,
    },
    computed: {

    },
    methods: {
    add:function(){
    this.bianliang += 1;
    },
    //点击按钮,循环 100 次,每次都渲染到页面上去
    test: function () {
    for(var i=0;i<100;i++){
    this.bianliang += 1;
    this.$forceUpdate(); //这个强制渲染是无效的,并不能让循环中 bianliang 的改变立即出现在页面上
    }

    },
    },

    });

    </script>
    abcbuzhiming
        11
    abcbuzhiming  
    OP
       2017-04-07 11:51:25 +08:00
    @coolzjy 其实我现在的问题是,为啥这个地方就必须是异步的呢?
    abcbuzhiming
        12
    abcbuzhiming  
    OP
       2017-04-07 11:53:10 +08:00
    @fszaer 你说的对,这就是我要的效果,实际的需求比这复杂的多,我只是抽象出了最简单的模型,总之就是,每次运算后立即刷新结果到页面上去,这个过程是链式过程,计算->刷新->计算->刷新->计算->刷新->......同步阻塞
    wly19960911
        13
    wly19960911  
       2017-04-07 12:07:43 +08:00
    你这个代码,没有时间怎么看得出来,这种一瞬间计算的东西就算是浏览器处理也是一瞬间的事情,你需要用动画的方式去处理,用 generator 或者 setTimeout (我感觉 setTimeout 太麻烦了)

    <div id="test"></div>
    <script>
    function test() {
    var node = document.getElementById('test');
    for(let a = 0 ; a < 100 ; a ++) {
    node.innerHTML = a;
    }
    }
    </script>

    你试试这段代码就知道,这种计算就是瞬间完成的。所以你需要使用异步去展示这段效果。
    ferrum
        14
    ferrum  
       2017-04-07 12:13:46 +08:00
    @abcbuzhiming 这个变量命名满分……

    我写了个 jsfiddle ,实际上$forceUpdate 是有效的,只是变量一下子从 0 到 100 ,中间没有间隔,看不出来变化而已。

    具体你可以看看[jsfiddle]( https://jsfiddle.net/6yj33wns/)
    maplerecall
        15
    maplerecall  
       2017-04-07 12:20:19 +08:00 via Android
    这难道不该用$nextTick 么, lz 的目的就是计算一次显示一次吧, async 写法把这个加入循环就好了
    coolzjy
        16
    coolzjy  
       2017-04-07 12:38:11 +08:00 via iPhone
    @abcbuzhiming 在极短时间内多次修改一个变量大多数时候是「与界面无关」的,如果这时候频繁操作 DOM 则会降低渲染效率。
    另一方面即使没有 Batch Update 机制,你这样在一个事件循环没多次操作 DOM 的做法也会被浏览器优化为一次渲染,而不会全部把过程显示出来。
    maplerecall
        17
    maplerecall  
       2017-04-07 12:41:17 +08:00 via Android
    @abcbuzhiming 你这个想法大体是没有问题的,但有两点,第一点非异步代码在执行完之前是不会刷新 dom 的,第二点电脑计算太快了,大多数显示器的刷新频率只有 60fps ,也就是每帧 16 毫秒, 16 毫秒内所做的任何改变你都是看不到的,会在下一帧才输出,可以去了解一下 requestAnimationFrame
    reus
        18
    reus  
       2017-04-07 12:52:25 +08:00
    不用 setTimeout 还想用什么……

    // 禁止点击按钮的代码放这里
    let update = (n) => {
    if (n == 0) {
    // 允许点击按钮的代码放这里
    return;
    }
    // 更新视图的代码放这里
    setTimeout(() => update(n - 1), 200);
    }

    根本就没 vue 什么事,它只负责显示。要什么效果,是你自己实现。不要以为用了框架,就什么都要框架帮你做。
    abcbuzhiming
        19
    abcbuzhiming  
    OP
       2017-04-07 13:09:47 +08:00
    @ferrum 变量名是我随便弄上去的,本身就是测试用的不要在意,其次谢谢你的范例, setTimeout 用的比我好。话说作为一个后端要理解 js 的箭头函数感觉好难啊。最后就是,其实你这个范例证明了我的判断,你可以把$forceUpdate 去掉,你就会发现,仍然会更新,因此不是$forceUpdate 本身在起效,而是异步过程在起效。这也证明了我的想法, Vue 似乎没办法在一次函数调用中 forceUpdate ,必须离开这个函数调用范围
    Alexisused
        20
    Alexisused  
       2017-04-07 13:11:15 +08:00 via Android
    计算一次 $nextTick 一次试试呢
    abcbuzhiming
        21
    abcbuzhiming  
    OP
       2017-04-07 13:32:02 +08:00
    @maplerecall 就从文档上来说,没看出 nextTick 这个方法要怎么用在我这种场景
    abcbuzhiming
        22
    abcbuzhiming  
    OP
       2017-04-07 13:33:16 +08:00
    @coolzjy 难道就没有一种机制强制浏览器刷新 dom 吗
    wly19960911
        23
    wly19960911  
       2017-04-07 13:39:29 +08:00 via Android
    @abcbuzhiming
    不要用后台那套来看前台,就算可以,那么意味着这套操作执行很慢,然后如果各相同种操作呢,这只是更新页面而已,还不够复杂,工作量再大一点用户直接崩溃了。

    实际上是你需要让动画堵塞-执行-堵塞,看看 async 和 generator 应该能解决。
    abcbuzhiming
        24
    abcbuzhiming  
    OP
       2017-04-07 13:42:58 +08:00
    @maplerecall 其实我奇怪的是这一点, 60 帧那个问题很好理解,我有做过游戏开发,但是“非异步代码执行完前不能刷新 Dom ”这点,是为什么呢,是浏览器机制限制? JavaScript 语言机制限制?因为我以前开发游戏的时候,某些引擎是可以在同步代码里强制刷新当前帧的
    finian
        25
    finian  
       2017-04-07 14:20:29 +08:00
    @abcbuzhiming #24 Vue.js 在一次 tick 中操作一次 dom ( diff, update ...),框架就是这么设计的。如果你非要即时刷新 dom ,你就直接操作 dom ( e.g. `node.innerHTML = xxx`),但是上面的同学都提到了,这个计算很快,你是看不到变化过程的。如果你是按照 Vue.js 的套路来玩的话,要做的事情就是让这个变量以一定间隔变化(不能太快),反映到视图上就是一个变化的“动画”过程。
    finian
        26
    finian  
       2017-04-07 14:42:09 +08:00
    yihouzenmeban
        27
    yihouzenmeban  
       2017-04-07 14:50:11 +08:00
    @ferrum #14 为什么要用 $forceUpdate 呀。。本身就是双向绑定的。。改变 data 就直接改变 view 层了诶。。(实测不用 $forceUpdate 也可以的
    xmflswood
        28
    xmflswood  
       2017-04-07 14:53:02 +08:00
    xmflswood
        29
    xmflswood  
       2017-04-07 14:53:36 +08:00
    看看异步更新队列那一节
    maplerecall
        30
    maplerecall  
       2017-04-07 14:54:22 +08:00
    @abcbuzhiming 非异步代码执行完前不能刷新 dom 这点是现在浏览器共同的做法, dom 重绘的开销非常大,所以在一段非异步代码内所有的 dom 操作只有在这段代码结束后才会渲染,目前并没有浏览器提供在当前强制重绘的接口,因为没有意义, js 是单线程的,你想把每次修改都反应出来又不想阻塞主进程,那就只能把所有操作都写成异步的,但即使异步中每次操作都修改了 dom 浏览器也不会马上刷新,也会等到下一个 frame 才重绘,如果你只是不想写 setTimeout 或者回调嵌套的话就用 async 直接把异步按照同步的方法来写。
    Sapp
        31
    Sapp  
       2017-04-07 16:58:22 +08:00
    实际上你修改了 他就会刷新啊,只要你加上间隔,让人眼可见不就可以了吗?这个和 vue 也没啥关系。
    kaneg
        32
    kaneg  
       2017-04-07 18:55:20 +08:00 via iPhone
    Javascript 是单线程,计算方法没有退出之前 UI 是没机会更新的
    445141126
        33
    445141126  
       2017-04-07 20:24:52 +08:00
    @kaneg js 里面还是可以强制同步重新渲染的 https://gist.github.com/paulirish/5d52fb081b3570c81e3a
    hst001
        34
    hst001  
       2017-04-07 20:30:43 +08:00
    现在好多前端框架主要就是解决这个问题来提供渲染性能的,你是逆行,可行的方法每加 1 就要手动更新一次 dom
    xieranmaya
        35
    xieranmaya  
       2017-04-07 21:11:45 +08:00
    大哥,你对 n 的操作是同步的吧??
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1266 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 23:32 · PVG 07:32 · LAX 15:32 · JFK 18:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.