3. Watch listeners, filters and computed properties in vue

watch, computed properties and filters

  • 1. watch (listener)
    • ==1, 1 shallow monitoring==
    • 1.2 Deep monitoring
    • 1.3 Summary
  • 2. filter (filter)
    • 2.1 Local filter
    • 2.2 Global filter
  • ==3. computed(computed attribute)==
    • 3.1 Basic use of computed
    • 3.2 Summary
    • 3. 3 Shopping cart case

1. watch(listener)

1, 1 shallow monitoring

  • Function: Monitor changes in data data
  • grammar:
 new Vue({
   watch:{
      name(newValue,oldValue){
       //oldValue: old value, the value before changing
       //newValue: new value, value after change
       //Business code
     }
   }
})
  • Case 1: Music list
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./vue.js"></script>
    <style>
        .box{<!-- -->
            margin: 20px;
            padding: 20px;
            border: 2px solid orangered;
        }
        .box span{<!-- -->
            display: inline-block;
            margin: 10px;
            padding: 10px;
            background: teal;
            color: #fff;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="app">
        <input type="text" v-model="kw">
     
        <div class="box" v-show="kw=='' ">
            <span v-for="(item,index) in hots" :key="index" @click=" kw=item ">{<!-- -->{item}}</span>
        </div>
    </div>
    <script>
        let vue = new Vue({<!-- -->
            el:"#app",
            data:{<!-- -->
                kw:"", //search keywords
                hots: ["Freedom", "The most dazzling national style", "Temptation of the wolf", "The man with the horse pole"]
            },
            methods:{<!-- -->},
            //Listen for changes in data
            watch:{<!-- -->
                kw(newValue){<!-- -->
                    //The data has changed and some expensive operations need to be done, ajax
                    console.log("Initiate ajax request, keyword: " + newValue);
                }
            }
        })
    </script>
</body>
</html>
  • Case 2—-Baidu search case
    Implementation principle:
    1.Principle of json
    Create a script tag: Initiate a request through the src of the script tag: Add the script tag to the page and receive the returned result through the callback function
    2. Layout, initialization
    3. Monitor changes in the content of the input box, and initiate a watch request when the change occurs.
    4. Initiate a request using jsonp method
    5. After obtaining the data, change the value of vue’s arr
    6. During the deletion process, if there is no data, an error will be reported when sending a request, and additional judgment will be made.
    7. Up and down arrows
  • code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./vue.js"></script>
    <style>
        .active{<!-- -->
            background:#ccc
        }
    </style>
</head>
<body>
    <div id="app">
        <p>Please enter search content: <input type="text" v-model="wd" @keydown.down="downFun" @keydown.up="upFun" @keydown.enter="enterFun"></p>
        <ul>
            <li v-for="(item,index) in arr" :key="item" :class="[index==n? 'active':'']">{<!-- -->{item }}</li>
        </ul>
    </div>
    <script>
       let vue = new Vue({<!-- -->
            el:"#app",
            //1.Initialize data
            data:{<!-- -->
                wd:"", //keyword
                arr:[],//array of searched content
                n:0, //Initialization subscript of style
            },
            methods:{<!-- -->
                //6.Down arrow
                downFun(){<!-- -->
                    this.n + + ;
                    if(this.n >= this.arr.length){<!-- -->
                        this.n = 0;
                    }
                },
                //7.Up arrow
                upFun(){<!-- -->
                    this.n--;
                    if(this.n < 0){<!-- -->
                        this.n = this.arr.length -1;
                    }
                },
                //8. Enter
                enterFun(){<!-- -->
                    window.open('https://www.baidu.com/s?wd=' + this.arr[this.n])
                }
            },
            //2. Monitor data changes
            watch:{<!-- -->
                wd(){<!-- -->
                    //5. Determine if there is value before initiating the request.
                    if(this.wd == ""){<!-- -->
                        this.arr = [];
                        return;
                    }

                    //3. Initiate a request to request backend data jsonp
                    let os = document.createElement("script");
                    os.src = 'http://suggestion.baidu.com/su?cb=callback & amp;wd=' + this.wd;
                    document.body.appendChild(os);
                }
            }
        })

        //The callback must be a global function
        function callback(res){<!-- -->
            //4. Change the value of arr of the vue instance. If the data changes, the view will also change accordingly.
            vue.arr = res.s;
        }
    </script>
</body>
</html>

1, 2 deep monitoring

  • Function: Monitor changes in reference data in data (array, object)
  • grammar
new Vue({
   watch:{
   //Shallow monitoring
    Monitored property name (newValue, oldValue){
    //oldValue: old value, the value before changing
    //newValue: new value, value after change
    //Business code
    },
    Monitored attribute name: {
    handler(new,old){
     //The function called when the data changes
    },
    deep:true/false //If the value of deep is true, depth monitoring
   }
  }
})
  • Case
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="app">
        <div>Reference data type (object, array): {<!-- -->{user}}</div>
        <button @click="change1">Change user</button>
        <div>Basic data types (strng, number, boolean): {<!-- -->{name}}</div>
        <button @click="change2">Change name</button>
    </div>
    <script>
        new Vue({<!-- -->
            el:"#app",
            data:{<!-- -->
                name:"eat rice",
                user:{<!-- -->
                    username:"xiaoming"
                }
            },
            methods:{<!-- -->
                //Change user
                change1(){<!-- -->
                    this.user.username = "daming";
                    //You can use events instead of deep monitoring
                },
                //Change NAME
                change2(){<!-- -->
                    this.name = "eating noodles"
                }
            },
            watch:{<!-- -->
                //Shallow monitoring
                // name(){}
                name:{<!-- -->
                    handler(){<!-- -->
                        console.log("name changed");
                    },
                    deep:false
                },
                //If you monitor objects or arrays, you need to use deep monitoring
                //It is not recommended to use deep monitoring. If there are too many deep monitoring, it will cause the page to freeze. It is recommended to use events instead of deep monitoring.
                user:{<!-- -->
                    handler(){<!-- -->
                        console.log("user changed");
                    },
                    deep:true
                }
            }
        })
    </script>
</body>
</html>
watch: monitor changes in data attributes
Shallow monitoring:
    watch:{
        Attribute name(new,old){

        }
            
If the monitoring is a reference data type, shallow monitoring cannot monitor data changes.
Deep monitoring:
 watch:{
    Monitored attribute name: {
        handler(new,old){
            
        },
        deep:true
    }
 }
 It is not recommended to use deep monitoring. Too much deep monitoring will cause the page to freeze. It is recommended to use events instead.

Summary of 1 and 3

  • No need to call, automatically called
  • Monitoring data changes can be done asynchronously, and if the overhead is relatively large, watch can do it
  • If the monitored data is an array or object, you need to use deep monitoring. It is not recommended to use deep monitoring. Too much deep monitoring will cause the page to freeze. It is recommended to use events instead of deep monitoring

2. filter

2, 1 local filter

  • Function: Convert data
In most cases, the data returned to us by the backend can be rendered directly, but sometimes, we need to perform secondary operations on the returned data.
example:
Mobile number: 13112345678 --> 131****5678
Time: 1696657349946 --> 2023/10/07
  • grammar
new Vue({
  filters:{
    Filter name (content to be filtered, parameters passed when calling the filter) {
        return returns the converted data result
    }
 }
})
  • use
<p>
     {<!-- -->{ Data to convert | Filter name }}
     {<!-- -->{ Data to be converted | Filter name (filter parameter) }}
</p>
    |pipe character
  • Case
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./vue.js"></script>
    <style>
        li{<!-- -->
            display: inline-block;
            padding: 20px;
            margin: 20px;
            border: 1px solid orangered;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- Filter usage Data to be filtered | Filter name -->
        <ul>
            <li v-for="item in goods" :key="item.id">
                <p>Product name: {<!-- -->{item.name}}</p>
                <!-- <p>Product price: {<!-- -->{item.price | filterTwo(2)}}</p> -->
                <p>Product price: {<!-- -->{item.price | filterTwo}}</p>
                <p>Item quantity: {<!-- -->{item.num}}</p>
                <p>Total product price: {<!-- -->{item.price * item.num | filterTwo(2)}}</p>
                <p>User phone number: {<!-- -->{item.tel | filterTel}}</p>
            </li>
        </ul>
    </div>
    <div id="app2">
        <h1>Telephone number: {<!-- -->{tel | filterTel}}</h1>
    </div>
    <script>
        //Global filter--to be defined before all vue instances, only one can be defined at a time/
        //Syntax: Vue.filter('filter name', (data to be filtered, parameters passed)=>{})
        Vue.filter("filterTel",(tel)=>{<!-- -->
            return tel.slice(0,3) + "****" + tel.slice(7)
        })
        new Vue({<!-- -->
            el:"#app",
            data:{<!-- -->
                goods:[
                    {<!-- -->id:1,name:"Litchi",num:5,price:49,tel:'13112345678'},
                    {<!-- -->id:2,name:"Grape",num:4,price:5.5,tel:'13112345678'},
                    {<!-- -->id:3,name:"Grapefruit",num:3,price:8.99,tel:'13112345678'},
                    {<!-- -->id:4,name:"Banana",num:2,price:3.45,tel:'13112345678'}
                ],
    
            },
            methods:{<!-- -->},
            //1. Local filter: can only be used in the current vue instance
            filters:{<!-- -->
                /*
                 Filter name (content to be filtered, parameters passed when calling the filter) {
                    return returns the converted data result
                 }
                */
                filterTwo(p,n=2){<!-- --> //p: data that needs to be filtered and does not need to be passed
                    console.log(p,n);
                    //Return the processed data
                    return p.toFixed(n);
                },
                filterTel(tel){<!-- -->
                    //'13112345678'
                    return tel.slice(0,3) + "****" + tel.slice(7)
                }
            }
        })
        new Vue({<!-- -->
            el:"#app2",
            data:{<!-- -->
                tel:"15167893245"
            }
        })
        
   </script>
</body>
</html>
  • Note: Local filters can only be used in the current vue instance and cannot be used in other vue instances

2, 2 global filters

  • Local filter: can only be used in the current vue instance, other vue instances cannot be used
  • Global filter: available to all vue instances before the vue instance is defined
  • grammar
Vue.filter("Filter name", (data to be filtered, parameters)=>{ return processed data })
//Global filter--to be defined before all vue instances, only one can be defined at a time/
  • Note: Global filters must be defined before all vue instances
  • Case
<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
   <script src="./vue.js"></script>
</head>
<body>
   <div id="app">
       <h3>News</h3>
       <ul>
           <li v-for="item in news" :key="item.id">
               <p>City: {<!-- -->{item.city}}</p>
               <p>News: {<!-- -->{item.news}}</p>
               <p>Date: {<!-- -->{item.time | filterTime}}</p>
           </li>
       </ul>
   </div>
   <script>
       //Define global filter
       Vue.filter("filterTime",(time)=>{<!-- -->
           //Convert a certain time format == Create a time object, obtain the corresponding year, month and day, and splice it
           let oDate = new Date(time*1); //new Date("2023-10-7") new Date(2023,10,7),new Date(timestamp)
           let y = oDate.getFullYear();
           let m = (oDate.getMonth() + 1 + "").padStart(2,'0');
           let d = (oDate.getDate() + "").padStart(2,'0');
           let h = (oDate.getHours() + "").padStart(2,'0');
           let mi = (oDate.getMinutes() + "").padStart(2,'0');
           let s = (oDate.getSeconds() + "").padStart(2,'0');
           return `${<!-- -->y}-${<!-- -->m}-${<!-- -->d} ${<!-- -->h}:$ {<!-- -->mi}:${<!-- -->s}`
       })

       new Vue({<!-- -->
           el:"#app",
           data:{<!-- -->
               news:[
                   {<!-- -->id:1,city:"Hangzhou",news:"Hangzhou Asian Games",time:"1696657349946"},
                   {<!-- -->id:2,city:"Beijing",news:"Beijing hahaha",time:"1696659454316"},
                   {<!-- -->id:3,city:"Shanghai",news:"Shanghai Hahaha",time:"1697657349946"}
               ]
           },
           methods:{<!-- -->},
       })
   </script>
</body>
</html>

3. computed (computed attribute)

3.1 Basic use of computed

  • Function: Calculated attributes
  • grammar
 new Vue({
      computed:{
        attribute name(){
          //Calculation logic return result
       }
     }
  })
  • Case
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="app">
        <table width="500" border="1">
            <thead>
                <tr>
                    <th>Number</th>
                    <th>Name</th>
                    <th>Achievements</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="item in students" :key="item.id">
                    <td>{<!-- -->{item.id}}</td>
                    <td>{<!-- -->{item.name}}</td>
                    <td><input type="text" v-model.number="item.score"></td>
                </tr>
                <tr>
                    <td>Grade average</td>
                    <td colspan="2">{<!-- -->{avagScore}}</td>
                </tr>
            </tbody>
        </table>

        <h4>Average score method: {<!-- -->{getAvag()}}</h4>
        <h4>Average score method: {<!-- -->{getAvag()}}</h4>
        <h4>Average score method: {<!-- -->{getAvag()}}</h4>
        <h4>Average score method: {<!-- -->{getAvag()}}</h4>
        <h4>Method - call once and calculate once</h4>
        
        <h4>Average score calculation attribute: {<!-- -->{avagScore}}</h4>
        <h4>Average score calculation attribute: {<!-- -->{avagScore}}</h4>
        <h4>Average score calculation attribute: {<!-- -->{avagScore}}</h4>
        <h4>Average score calculation attribute: {<!-- -->{avagScore}}</h4>
        <h4>Computed attributes - there is a cache, and the calculation will only be called again when the data changes</h4>
    </div>
    <script>
        new Vue({<!-- -->
            el: "#app",
            data: {<!-- -->
                students: [
                    {<!-- --> id: 1, name: "Liu Bei", score: 99 },
                    {<!-- --> id: 2, name: "Zhuge Liang", score: 100 },
                    {<!-- --> id: 3, name: "Sun Ce", score: 50 },
                    {<!-- --> id: 4, name: "Sima Yi", score: 85 },
                    {<!-- --> id: 5, name: "Xiao Qiao", score: 70 }
                ],
            },
            methods: {<!-- -->
                getAvag() {<!-- -->
                    console.log("Calculated by method");
                    //Calculate average
                    let sum = 0;
                    this.students.forEach(value => {<!-- -->
                        sum + = value.score;
                    });
                    //return average score
                    return (sum / this.students.length).toFixed(2)
                }
            },
            //mounted(){} Once the page is rendered, you need to calculate the score.
            //Computed property: If you need to calculate the value, you will use the calculated property.
            //Depends on data: If the data changes, it will be recalculated
            computed: {<!-- -->
                //Attribute name(){ calculation return calculation result }
                avagScore() {<!-- -->
                    console.log("Computed Property");
                    //Calculate average
                    let sum = 0;
                    this.students.forEach(value => {<!-- -->
                        sum + = value.score;
                    });
                    //return average score
                    return (sum / this.students.length).toFixed(2)
                }
            }
        })
    </script>
</body>
</html>
  • Note: Calculated properties are cached, and the calculation will only be re-called when the data changes

3, 2 Summary

  • To call, use {{name}} as a normal attribute without adding ()
  • Dependent data, if the dependent data changes, the calculation will be called again
  • Do synchronization operations
  • What is the difference between computed properties and methods?
 The same point: their results are determined by dependent data, and will be recalculated when the dependent data changes.
 difference:
     Computed properties have a cache effect. No matter how many times they are called, the first calculation result will be applied as long as the data remains unchanged.
     The method is to call it several times and calculate how many times
  • The difference between computed and watch
 wacth is one-to-one, monitors changes in an attribute, and performs multiple events and asynchronous operations.
  computed many-to-one, that is, you can rely on multiple data, but the result is one, synchronous operation

3, 3 shopping cart cases

  • renderings
  • code
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./vue.js"></script>
    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css">
    <style>
        img {<!-- -->
            width: 100px;
            height: 100px;
        }
    </style>
</head>
<body>
    <div id="app" class="container">
        <!-- 1. Layout -->
        <table class="table table-bordered">
            <thead>
                <tr>
                    <th><input type="checkbox" @change="changeAll" v-model="isAll"> Select all</th>
                    <th>Product information</th>
                    <th>Unit price of product</th>
                    <th>Quantity of items</th>
                    <th>Item Subtotal</th>
                    <th>Operation</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="(item,index) in goods" :key="item.id">
                    <td><input type="checkbox" v-model="item.isChecked" @change="changeOne"></td>
                    <td>
                        <strong>{<!-- -->{item.name}}</strong>
                        <img :src="item.img" alt="">
                    </td>
                    <td>{<!-- -->{item.price}}</td>
                    <td>
                        <button type="button" class="btn btn-defalut" @click="sub(index)">-</button>
                        {<!-- -->{item.num}}
                        <button type="button" class="btn btn-primary" @click="add(index)"> + </button>
                    </td>
                    <td>{<!-- -->{item.price*item.num}}</td>
                    <td><button type="button" class="btn btn-danger" @click="delItem(index)">Delete</button></td>
                </tr>
                <tr>
                    <td>Total:</td>
                    <td colspan="5">{<!-- -->{total}}</td>
                </tr>
            </tbody>
        </table>
    </div>
    <script>
        //10. Global filter, retain 2 decimal places
        // Backend data
        let mock = [
            {<!-- -->
                id: 1,
                img: "https://img10.360buyimg.com/n7/jfs/t1/116325/12/33822/74207/642ec4caF5b53c8a7/8c548ca792f592fc.jpg",
                name: "mobile phone",
                price: 2999.9,
                num: 3
            },
            {<!-- -->
                id: 2,
                img: "https://img12.360buyimg.com/n7/jfs/t1/72688/18/26431/128682/6433e218F83aa27a3/6e79bf181755bf44.jpg.avif",
                name: "computer",
                price: 13145.9,
                num: 1
            },
            {<!-- -->
                id: 3,
                img: "https://img14.360buyimg.com/n7/jfs/t1/105370/22/32732/131023/646effdaFdff68a3f/ffeecdc7067016bd.jpg.avif",
                name: "ipad pro",
                price: 5999,
                num: 1
            }
        ]
        new Vue({<!-- -->
            el: "#app",
            data: {<!-- -->
                goods: [],
                isAll:false
            },
            methods: {<!-- -->
                //3. Click Delete to delete the corresponding item
                delItem(index){<!-- -->
                    this.goods.splice(index,1);
                },
                //4. Click +, quantity + 1
                add(index){<!-- -->
                    //Judge, cannot exceed 5
                    // this.goods[index].num < 5 ? + + this.goods[index].num : this.goods[index].num = 5
                    
                    //Boundary value judgment Math.min(4,5)
                    this.goods[index].num = Math.min(5, + + this.goods[index].num);
                },
                //5.Click-, quantity-1
                sub(index){<!-- -->
                    //Boundary value judgment Math.min(4,5)
                    this.goods[index].num = Math.max(1,--this.goods[index].num);
                },
                //7. Click the Select All button to let all products change status.
                changeAll(){<!-- -->
                    // console.log("Select all changed", this.isAll);
                    //Change the selection status of all products, the same as selecting all
                    this.goods.forEach(value=>{<!-- -->
                        value.isChecked = this.isAll;
                    });
                    console.log(this.goods);
                },
                //8. Click the input of the product. If you select all, the select all button also needs to be selected.
                changeOne(){<!-- -->
                    this.isAll = this.goods.every(value=>{<!-- -->
                        return value.isChecked == true
                    });
                }
            },
            //2. Request data--start requesting data after the mounting is completed
            mounted(){<!-- -->
                //Initiate ajax
                // this.goods = mock;
                // //6. Give the input tag a checked attribute, but the backend does not give it. Add it yourself after requesting the data.
                // this.goods.forEach(value=>{<!-- -->
                // value.isChecked=false;
                // });
                // console.log(this.goods);

                //Not defined in data, attributes added later are not responsive
                //Process the data first, and then assign it to the attributes in data. In this way, it is defined in data, and there will be responsiveness.
                mock.forEach(value=>{<!-- -->
                    value.isChecked=false;
                });
                this.goods = mock;
            },
            //9. Calculate the total price
            computed:{<!-- -->
                total(){<!-- -->
                    //Loop +
                    let sum = 0;
                    this.goods.forEach(value=>{<!-- -->
                        //The selected ones +
                        if(value.isChecked){<!-- -->
                            sum + = value.price * value.num;
                        }
                    });
                    return sum;
                }
            }
        })
    </script>
</body>
</html>