面试官:如何实现就地编辑?你这样回答....

951 阅读5分钟

前言

作为一名前端"资深"切图仔,想要成为高级开发工程师的话,用户的体验感极为重要,今天我们就来学习如何是实现这道面试题,让用户感受js的高级用户体验——就地编辑。

业务场景

不知道jym是否发现,我们经常使用的b站,在b站的个人主页页面内,有一个个性签名,也就是下面这个,它就是用就地编辑功能,让用户随时都可以对自己的个性签名进行修改或删除,大大提高用户的使用体验。

image.png

  • 就地编辑的需求分析:
    1. 平时是文本状态,显示签名
    2. mouseover 事件 进入编辑状态,显示编辑框
    3. 退出编辑状态时 自动保存,切换到文本状态

那这样就很简单了,创建所需的DOM元素,然后加上事件监听,修改DOM元素的展示状态,文本状态和编辑状态相互转换,就可以完成了文本和编辑的切换了。 image.png

image.png

实践了的jym就会发现,数据都没被记录下来,当页面刷新时,又回到了初始状态,所以,我们需要把输入的文本保存到数据库内。而文本内的数据显示数据库,所以我们现在需要一个模拟的数据库,需要用到json-server

这里简单介绍一下

json-server是一个简单的命令行工具,它用于创建一个假的API服务器,主要用于模拟JSON文件或数据库中的数据。以下是json-server的一些主要特点:

  1. 快速设置:只需要几分钟就可以启动并运行一个RESTful API(资源接口)。
  2. 静态JSON文件:你可以使用一个JSON文件作为数据源,json-server会将其转换为API端点。
  3. 数据库支持:除了JSON文件,它还支持SQLite、MongoDB等数据库。
  4. 自动路由:根据你的数据结构自动生成GET, POST, PUT, DELETE等HTTP方法的路由。
  5. 延迟模拟:可以添加网络延迟,以模拟真实世界中的网络状况。
  6. 中间件支持:允许你添加自定义中间件来处理更复杂的逻辑。

要使用json-server,首先需要通过npm(Node.js包管理器)安装它。安装完成后,可以通过指定一个JSON文件或数据库来启动服务器。

  • 初始化一个文件为仿后端数据库json文件:

    1. 创建一个后端文件夹
    2. npm init -y 初始化 package.json 配置
    3. npm i json-server 安装json-server这个本地开发依赖
    4. 在后端项目文件内创建一个模拟的数据库json文件 db.json
    5. scripts 脚本区域 json-server --watch db.json (此处需要自己配置)这将启动一个监听在默认端口3000上的服务器
    6. 在后端文件终端下启动 -> npm run dev
  • 下面是db.json内的文件,和访问的模拟API:

    image.png

  • 网页打开:http://localhost:3000/users:

    image.png

  • 如果要访问第一个数据对象,可以访问http://localhost:3000/users/1

    image.png

前端代码如下:

<body>
    <div id="signature"></div>
    <script src="./edit_in_place.js"></script>
    <script>
        fetch("http://localhost:3000/users/1")
        .then(
            res => res.json()
        ).then(data =>{
            console.log(data,"////////")
            const {signature} = data; // es6 的解构特性
            new EditInPlace(document.getElementById('signature'));
        }) 
    </script>
</body>

解决刷新存储不到值的问题: 当我们刚打开页面时,可以向后端发送数据请求,在返回的json数据内找到你之前存储在数据库内的signature,将其显示,然后修改signature时直接将输入框内的值存入数据库,这样形成一个闭环,达到存储目的。

这里的fetch()函数是ES6新封装的异步请求方式,它是基于Promise的API,用于替代传统的XMLHttpRequest,后面会详细了解一些promise的,这里做简单了解。

那么我们再来看edit_in_place.js文件内容:创建DoM元素我们就不看了,上面有截图,全部代码以放最后。

  • 构造函数初始化先创建了DOM元素节点,然后再进行事件监听,在创建DOM的时候默认初始化为文本状态的显示。 image.png

  • 在事件监听函数内,一定要注意this的丢失情况,在事件监听时,一般可以利用箭头函数没this或使用的是外部的this的特性,或者在事件监听执行前对外部this进行存储,就像我的截图里一样。

  • 因为this在函数表达式中的绑定是在函数创建时确定的,而不是在调用时确定的。在这种情况下,this通常会绑定到全局对象(在浏览器中是window)或者在严格模式下('use strict';)为undefinedimage.png

  • 这里即为保存按钮事件触发,将输入框的值赋给静态文本元素,然后由编辑状态转为文本状态,顺便更新数据库。 image.png

  • 下面即为浏览器展示效果: image.png

  • 点击文本进行切换,

    image.png

edit_in_place.js代码如下

function EditInPlace(container,value = '这个家伙很懒,什么都没有留下'){
    this.container = container;
    this.value = value;
    // 动态创建文本和编辑input和dom 封装,代码的管理好
    this.createElement();
    this.attachEvents();
} 
EditInPlace.prototype = {
    // 就地编辑的动态DOM
    createElement: function(){
        // 操作DOM树
        // 创建一个div
        this.editElement = document.createElement('div');
        //添加一个子元素
        this.container.appendChild(this.editElement);  
        // signature 文本值
        this.staticElement = document.createElement('span');
        this.staticElement.innerHTML = this.value;
        this.editElement.appendChild(this.staticElement);

        // input 输入框
        this.fieldElement = document.createElement('input')
        this.fieldElement.type = 'text';
        this.fieldElement.value = this.value;
        this.editElement.appendChild(this.fieldElement);
        // 确定按钮
        this.savaButton = document.createElement('input');
        this.savaButton.type = 'button';
        this.savaButton.value = '保存';
        this.editElement.appendChild(this.savaButton);

        // 取消按钮
        this.cancelButton = document.createElement('input');
        this.cancelButton.type = 'button';
        this.cancelButton.value = '取消';
        this.editElement.appendChild(this.cancelButton);
        // 初始文本状态
        this.converToText()
    },
    // 文本状态 
    converToText: function(){
        this.staticElement.style.display = 'inline';
        this.fieldElement.style.display = 'none';
        this.savaButton.style.display = 'none';
        this.cancelButton.style.display = 'none';
    },
    // 编辑状态
    converToEdit: function(){
        this.staticElement.style.display = 'none';
        this.fieldElement.style.display = 'inline';
        this.savaButton.style.display = 'inline';
        this.cancelButton.style.display = 'inline';
    },
    // 事件监听
    attachEvents: function(){
        // this 指向外层
        this.staticElement.addEventListener('click',()=>{
            // this指向元素
            this.converToEdit();  
        })
        // let that = this;
        // this.staticElement.addEventListener('click',function(){
        //     this丢失了
        //     that.converToEdit();  // 防止创建函数时this丢失
        // })
        this.savaButton.addEventListener('click',() => {
            this.save();
        })
        this.cancelButton.addEventListener('click',()=>{
            this.converToText();
        })
    },
    save: function(){
        this.value = this.fieldElement.value;
        this.staticElement.innerHTML = this.value;
        this.converToText();
        this.saveData()
    },
    saveData:function(){
        let value = this.value;
        // GET-->读  POST-->创建  PUT --> 更新  PATCH --> 局部更新 DELETE --> 删除
        fetch('http://localhost:3000/users/1',{
            method: 'PATCH',
            headers: {  // 请求头 发送的内容 以json格式发送
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({  // 请求体  
                signature: value
            })
        })
        .then(res => res.json())
        .then(data=>{
            console.log(data,'保存成功');
        })
    }
}

总结

总算是说完了,感谢jym的观看哦,看到这里你已经很牛了,虽然文字有点多,但是我们都能从中学到很多,前端嘛,就该给用户更高级的体验感,这样才能留住用户。可能方法不是最好最优,以后慢慢更改,慢慢积累吧,让我们一起冲起来!