一篇文章全面了解Ajax框架

目录

什么是AJAX?

如何使用AJAX?

语法

url详解

URL的概念

URL的组成

URL查询参数

axios

axios-查询参数

axios-请求配置

常用请求方法

axios-错误处理

HTTP协议

HTTP协议-请求报文

HTTP协议-响应报文

接口文档

AJAX原理

[AJAX原理 - XMLHttpRequest](#AJAX原理 - XMLHttpRequest)

Promise

[Promise - 三种状态](#Promise - 三种状态)

AJAX封装


什么是AJAX?

概念:AJAX是浏览器与服务器进行数据通信的技术。

定义:AJAX是异步的JavaScript和XML(Asynchronous JavaScript And XML)。简单点说,就是使用XMLHttpRequest对象与服务器通信。它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。AJAX最吸引人的就是它的"异步"特性,也就是说它可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。


如何使用AJAX?

语法

  1. 引入axios.js:https://cdn.jsdelivr.net/npm/axios/dist/axios/min.js
  2. 使用axios函数
    1. 传入配置对象
    2. 再用**.then**回调函数接收结果,并做后续处理

接下来让我们通过代码演示看看ajax的作用吧,代码、需求及效果图如下:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ajax</title>
</head>
<body>
    <!--
        axios库地址:https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
        省份数据地址:http://hmajax.itheima.net/api/province

        目标:使用axios库,获取省份列表数据,展示到页面上
    -->
    <!-- 渲染 -->
    <p class="my-p"></p>
    <!-- 1.引入axios库 -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <!-- 2.使用axios函数 -->
    <script>
        axios({
            url:'http://hmajax.itheima.net/api/province'
        }).then(result => {
            // 打印返回结果
            console.log(result)
            // 打印省份列表
            console.log(result.data.list)
            // 打印省份
            console.log(result.data.list.join('<br>'))
            // 插入页面
            document.querySelector('.my-p').innerHTML = result.data.list.join('<br>')
        })
    </script>
</body>
</html>

可以看到,通过axios函数我们可以从url中获取数据资源,并进行处理,那么url是什么意思呢?接下来我们进行详细的了解。


url详解

URL的概念

Internet上的每一个网页都具有一个唯一的名称标识,通常称之为URL(Uniform Resource Locator, 统一资源定位器)。

它是www的统一资源定位标志,简单地说URL就是web地址,俗称"网址"。

URL的组成

URL由三部分组成:资源类型、存放资源的主机域名、资源文件名

也可认为由4部分组成:协议、主机、端口、路径。(很多时候端口都是隐藏的)

还可以认为由7部分组成:协议,域名,端口,虚拟目录,文件名,锚,参数

URL的一般语法格式:(带方括号[]的为可选项):protocol :// hostname[:port] / path / [;parameters][?query]#fragment
**案例:**https://www.baidu.com

协议:https

**域名(IP):**www.baidu.com

端口(port):443

资源路径:当没有资源路径时,可以默认是 / ,在各个网站通常有默认的路径如index.html

URL查询参数

定义:浏览器提供给服务器的额外信息,让服务器返回浏览器想要的数据

语法:http://xxxx.com/xxx/xxx**?**参数名1=值1**\&**参数名2=值2

示例:假如前面的案例,我要获取河北省下辖市的信息,就可以写为:

http://hmajax.itheima.net/api/city?pname=河北省

axios

现在开始,我们对axios进行全面的了解和使用。

axios-查询参数

语法:使用axios提供的params选项

注意:axios在运行时把参数名和值,会拼接到url**?参数名=值**

javascript 复制代码
axios({
    url:'目标资源地址'
    params:{
        参数名:值
    }
}).then(result=>{
    //对服务器返回的数据做后续处理
})

那么让我们来使用查询参数查询一下云南省下辖市吧:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>查询参数</title>
</head>
<body>
    <p></p>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
        axios({
            url:'http://hmajax.itheima.net/api/city',
            //查询参数
            params:{
                pname:'云南省'
            }
        }).then(result => {
            console.log(result.data.list)
            document.querySelector('p').innerHTML = result.data.list.join('<br>')
        })
    </script>
</body>
</html>

渲染图如上所示,可以看到我们的确拿到了对应的数据,那么此时我们就完成了对查询参数的学习了。

axios-请求配置

常用请求方法

现在我们已经可以从服务器拿到对应的数据了,那么接下来我们来学习一下然后对服务器数据做其他的相关操作:

请求方法:对服务器资源,要执行的操作

|--------|----------|
| 请求方法 | 操作 |
| GET | 获取数据 |
| POST | 提交数据 |
| PUT | 修改数据(全部) |
| DELETE | 删除数据 |
| PATCH | 修改数据(部分) |


那么接下来我们就看看axios请求配置该如何操作吧:

  • url:请求的URL网址

  • method :请求的方法,GET可以省略(不区分大小写)

  • data:提交数据

    axios({
    url: '目标资源地址',
    method: '请求方法',
    data: {
    参数名:值
    }
    }).then((result)=>{
    // 对服务器返回的数据做后续处理
    })

接下来我们使用POST完成一个案例吧:

<!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>常用请求方法和数据提交</title>
</head>

<body>
  <button class="btn">注册用户</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /*
      注册用户:http://hmajax.itheima.net/api/register
      请求方法:POST
      参数名:
        username:用户名(中英文和数字组成,最少8位)
        password:密码  (最少6位)

      目标:点击按钮,通过axios提交用户和密码,完成注册
    */
    document.querySelector('.btn').addEventListener('click', () => {
      axios({
        url: 'http://hmajax.itheima.net/api/register',
        method: 'POST',
        data: {
          username: 'qingwan666',
          password: 'qingwan666'
        }
      }).then(result => {
        console.log(result);
      })
    })
  </script>
</body>

</html>

打开网页-点击按钮后效果图->

再次点击按钮效果图->

axios-错误处理

通过刚才的案例,我们发现第二次点击按钮之后会输出错误信息,因为我们第二次点击注册按钮,原本的账号已经被我们注册过了,在服务器中已经不是唯一的了,所以给我们返回了一个报错信息,提示账号被占用,那么我们如何处理这种情况呢?

语法 :在then 方法的后面,通过点语法调用catch 方法,传入回调函数 并定义形参

axios({
    //请求选项
}).then(result=>{
    //处理数据
}).catch(error=>{
    //处理错误
})

接下来我们继续使用刚刚的案例来完成错误处理的代码,需求:重复注册时通过弹窗提示用户错误原因

html 复制代码
<!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>axios错误处理</title>
</head>

<body>
  <button class="btn">注册用户</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    /*
      注册用户: http://hmajax.itheima.net/api/register
      请求方法: POST
      参数名:
        username: 用户名 (中英文和数字组成, 最少8位)
        password: 密码 (最少6位)

      目标: 点击按钮, 通过axios提交用户和密码, 完成注册
      需求: 使用axios错误处理语法, 拿到报错信息, 弹框反馈给用户
    */
   document.querySelector('.btn').addEventListener('click', () => {
    axios({
      url: 'http://hmajax.itheima.net/api/register',
      method: 'post',
      data: {
        username: 'qingwan666',
        password: 'qingwan666'
      }
    }).then(result => {
      // 成功
      console.log(result)
    }).catch(error => {
      // 失败
      // 处理错误信息
      console.log(error)
      console.log(error.response.data.message)
      alert(error.response.data.message)
    })
   })
  </script>
</body>

</html>

点击按钮重复注册后->

这里就可以看到,重复注册时,浏览器已经能够通过弹窗提示用户了。


HTTP协议

那么关于Post和Get......,我们要如何在浏览器中看到对应的信息呢?它们的原理是什么呢?接下来我们要开始针对HTTP进行学习。

HTTP协议:规定了浏览器发送及服务器返回内容的格式

HTTP协议-请求报文

请求报文:浏览器按照HTTP协议要求的格式 ,发送给服务器的内容

请求报文的组成:

  1. 请求行:请求方法,URL,协议
  2. 请求头:以键值对的格式携带的附加信息,比如:Content-Type
  3. 空行:分割请求头,空行之后的是发送给服务器的资源
  4. 请求体:发送的资源

接下来我们继续用前面的案例,来看看HTTP的请求报文:

网络->左侧选择名称->标头->原始:这里就可看到请求行与请求头了

再点击负载:即可查看请求体

HTTP协议-响应报文

响应报文:服务器按照HTTP协议要求的格式 ,返回给浏览器的内容

响应报文的组成:

  1. 响应行(状态行):协议、HTTP响应状态码、状态信息
  2. 响应头:以键值对的格式携带的附加信息,比如:Content-Type
  3. 空行:分割响应头,空行之后的是发送给服务器的资源
  4. 响应体:返回的资源

HTTP响应状态码:用来表明请求是否成功完成

以下是各个状态码说明。例如:404(服务器找不到资源)

|-----|-------|
| 状态码 | 说明 |
| 1xx | 信息 |
| 2xx | 成功 |
| 3xx | 重定向消息 |
| 4xx | 客户端错误 |
| 5xx | 服务端错误 |

查看响应报文的方式与请求报文大致相同,只需要将请求标头换为响应标头,负载换为响应即可

接口文档

接口文档:描述接口的文章(后端工程师提供)

接口:使用AJAX和服务器通讯时,使用的URL,请求方法,以及参数

举例:下图为登录接口文档

这里给了我们请求方法、URL,以及需要的参数,那么我们用代码来写一下登入功能:

html 复制代码
<!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>接口文档</title>
</head>

<body>
  <button class="btn">用户登录</button>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  <script>
    document.querySelector('.btn').addEventListener('click', () => {
      // 用户登录
      axios({
        url: 'http://hmajax.itheima.net/api/login',
        method: 'post',
        data: {
          username: 'qingwan666',
          password: 'qingwan666'
        }
      })
    })
  </script>
</body>

</html>

点击登录按钮->

以上就是接口文档的使用教程啦,接下来我们进入下一步的学习。

AJAX原理

AJAX原理 - XMLHttpRequest

定义:XMLHttpRequest(XHR)对象用于与服务器交互,通过XMLHttpRequest可以在不刷新页面的情况下请求特定URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest在AJAX变成中被大量使用。

关系:axios内部采用XMLHttpRequest与服务器交互

步骤:

  1. 创建XMLHttpRequest对象

  2. 配置请求方法 和请求url地址

  3. 监听loadend事件,接收响应结果

  4. 发起请求

    const xhr = new XMLHttpRequest()
    xhr.open('请求方法', '请求url网址')
    xhr.addEventListener('loadend', () => {
    //响应结果
    console.log(xhr.response)
    })
    //发送请求
    xhr.send()

我们继续用前面获取地区的案例来演示一下原生XML如何使用:

html 复制代码
<!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>XMLHttpRequest_基础使用</title>
</head>

<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:使用XMLHttpRequest对象与服务器通信
     *  1. 创建 XMLHttpRequest 对象
     *  2. 配置请求方法和请求 url 地址
     *  3. 监听 loadend 事件,接收响应结果
     *  4. 发起请求
    */
    // 1. 创建 XMLHttpRequest 对象
    const xhr = new XMLHttpRequest()

    // 2. 配置请求方法和请求 url 地址
    xhr.open('GET', 'http://hmajax.itheima.net/api/province')

    // 3. 监听 loadend 事件,接收响应结果
    xhr.addEventListener('loadend', () => {
      console.log(xhr.response)
      const data = JSON.parse(xhr.response)
      console.log(data.list.join('<br>'))
      document.querySelector('.my-p').innerHTML = data.list.join('<br>')
    })

    // 4. 发起请求
    xhr.send()
  </script>
</body>

</html>

网页效果图->

接下来我们看看原生XML查询参数的使用案例:

html 复制代码
<!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>XMLHttpRequest_查询参数</title>
</head>

<body>
  <p class="city-p"></p>
  <script>
    /**
     * 目标:使用XHR携带查询参数,展示某个省下属的城市列表
    */
    const xhr = new XMLHttpRequest()
    xhr.open('GET', 'http://hmajax.itheima.net/api/city?pname=辽宁省')
    xhr.addEventListener('loadend', () => {
      console.log(xhr.response)
      const data = JSON.parse(xhr.response)
      console.log(data)
      document.querySelector('.city-p').innerHTML = data.list.join('<br>')
    })
    xhr.send()
  </script>
</body>

</html>

再看看如何提交数据:

html 复制代码
<!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>XMLHttpRequest_数据提交</title>
</head>

<body>
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:使用xhr进行数据提交-完成注册功能
    */
    document.querySelector('.reg-btn').addEventListener('click', () => {
      const xhr = new XMLHttpRequest()
      xhr.open('POST', 'http://hmajax.itheima.net/api/register')
      xhr.addEventListener('loadend', () => {
        console.log(xhr.response)
      })

      // 设置请求头-告诉服务器内容类型(JSON字符串)
      xhr.setRequestHeader('Content-Type', 'application/json')
      // 准备提交的数据
      const userObj = {
        username: 'itheima007',
        password: '7654321'
      }
      const userStr = JSON.stringify(userObj)
      // 设置请求体,发起请求
      xhr.send(userStr)
    })
  </script>
</body>

</html>

学习原生XML只是为了了解axios的原理,帮助我们更加方便的使用它,继续开始下一步的学习


Promise

由于AJAX是异步请求,对于有些场景,如果请求失败,那么需要如何操作呢?

这时候我们就需要Promise来帮助我们管理

定义:Promise对象用于表示一个异步操作的最终完成(或失败)及其结果值。

我们再了解下学习Promise有什么好处:

  • 逻辑更清晰
  • 了解axios函数内部运作机制
  • 能解决回调函数地狱问题
html 复制代码
// 1.创建Promise对象
const p = new Promise((resolve, reject) => {
    //2.执行异步任务-并传递结果
    //成功调用:resolve(值) 触发then()执行
    //失败调用:reject(值) 触发catch()执行
})
// 3.接收结果
p.then(result => {
    //成功
}).catch(error => {
    //失败
})

让我们用代码看看Promise吧:

html 复制代码
<!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>认识Promise</title>
</head>

<body>
  <script>
    /**
     * 目标:使用Promise管理异步任务
    */
    // 1. 创建Promise对象
    const p = new Promise((resolve, reject) => {
      // 2. 执行异步代码
      setTimeout(() => {
        // resolve('模拟AJAX请求-成功结果')
        reject(new Error('模拟AJAX请求-失败结果'))
      }, 2000)
    })

    // 3. 获取结果
    p.then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    })
  </script>
</body>

</html>

Promise - 三种状态

作用:了解Promise对象如何关联处理函数,以及代码执行顺序

  1. new Promise() 此时的Promise状态:待定
  2. resolve() 此时的Promise状态:已兑现(.then(回调函数))
  3. reject() 此时的Promise状态:已拒绝(.catch(回调函数))

待定(pending):初始状态,既没有被兑现,也没有被拒绝

已兑现(fulfilled):意味着,操作成功完成

已拒绝(rejected):意味着,操作失败

注意:Promise对象一旦被兑现/拒绝,就无法改变状态了


AJAX封装

现在我们已经学习了XML和Promise,也就是axios内部最核心的两个技术我们都已经掌握,那么现在我们来像axios函数一样进行一个调用。

需求:基于Promise + XHR封装myAxios函数,获取省份列表展示

步骤:

  1. 定义myAxios函数,接收配置兑现 ,返回Promise对象
  2. 发起XHR请求,默认请求方法为GET
  3. 调用成功/失败的处理程序
  4. 使用myAxios函数,获取省份列表展示

代码实现:

html 复制代码
<!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>封装_简易axios函数_获取省份列表</title>
</head>

<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取省份列表
     *  1. 定义myAxios函数,接收配置对象,返回Promise对象
     *  2. 发起XHR请求,默认请求方法为GET
     *  3. 调用成功/失败的处理程序
     *  4. 使用myAxios函数,获取省份列表展示
    */
    // 1. 定义myAxios函数,接收配置对象,返回Promise对象
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        // 2. 发起XHR请求,默认请求方法为GET
        const xhr = new XMLHttpRequest()
        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          // 3. 调用成功/失败的处理程序
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 4. 使用myAxios函数,获取省份列表展示
    myAxios({
      url: 'http://hmajax.itheima.net/api/province'
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    }).catch(error => {
      console.log(error)
      document.querySelector('.my-p').innerHTML = error.message
    })
  </script>
</body>

</html>

网页部分效果图->

再看看axios查询参数的封装案例吧:

html 复制代码
<!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>封装_简易axios函数_获取地区列表</title>
</head>

<body>
  <p class="my-p"></p>
  <script>
    /**
     * 目标:封装_简易axios函数_获取地区列表
     *  1. 判断有params选项,携带查询参数
     *  2. 使用URLSearchParams转换,并携带到url上
     *  3. 使用myAxios函数,获取地区列表
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        // 1. 判断有params选项,携带查询参数
        if (config.params) {
          // 2. 使用URLSearchParams转换,并携带到url上
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          // 把查询参数字符串,拼接在url?后面
          config.url += `?${queryString}`
        }

        xhr.open(config.method || 'GET', config.url)
        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        xhr.send()
      })
    }

    // 3. 使用myAxios函数,获取地区列表
    myAxios({
      url: 'http://hmajax.itheima.net/api/area',
      params: {
        pname: '辽宁省',
        cname: '大连市'
      }
    }).then(result => {
      console.log(result)
      document.querySelector('.my-p').innerHTML = result.list.join('<br>')
    })

  </script>
</body>

</html>

最后看看提交数据的封装,我们也就完成对AJAX的学习了:

html 复制代码
<!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>封装_简易axios函数_注册用户</title>
</head>

<body>
  <button class="reg-btn">注册用户</button>
  <script>
    /**
     * 目标:封装_简易axios函数_注册用户
     *  1. 判断有data选项,携带请求体
     *  2. 转换数据类型,在send中发送
     *  3. 使用myAxios函数,完成注册用户
    */
    function myAxios(config) {
      return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

        if (config.params) {
          const paramsObj = new URLSearchParams(config.params)
          const queryString = paramsObj.toString()
          config.url += `?${queryString}`
        }
        xhr.open(config.method || 'GET', config.url)

        xhr.addEventListener('loadend', () => {
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            reject(new Error(xhr.response))
          }
        })
        // 1. 判断有data选项,携带请求体
        if (config.data) {
          // 2. 转换数据类型,在send中发送
          const jsonStr = JSON.stringify(config.data)
          xhr.setRequestHeader('Content-Type', 'application/json')
          xhr.send(jsonStr)
        } else {
          // 如果没有请求体数据,正常的发起请求
          xhr.send()
        }
      })
    }
  
    document.querySelector('.reg-btn').addEventListener('click', () => {
      // 3. 使用myAxios函数,完成注册用户
      myAxios({
        url: 'http://hmajax.itheima.net/api/register',
        method: 'POST',
        data: {
          username: 'itheima999',
          password: '666666'
        }
      }).then(result => {
        console.log(result)
      }).catch(error => {
        console.dir(error)
      })
    })
  </script>
</body>

</html>
相关推荐
m0_7482517219 分钟前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·vue.js·ajax
上等猿22 分钟前
Ajax笔记
前端·笔记·ajax
Amo 672924 分钟前
css 编写注意-1-命名约定
前端·css
匹马夕阳30 分钟前
详细对比JS中XMLHttpRequest和fetch的使用
开发语言·javascript·ecmascript
长风清留扬1 小时前
小程序开发实战项目:构建简易待办事项列表
javascript·css·微信小程序·小程序·apache
程序员_三木1 小时前
从 0 到 1 实现鼠标联动粒子动画
javascript·计算机外设·webgl·three.js
点点滴滴的记录1 小时前
Java的CompletableFuture实现原理
java·开发语言·javascript
程序猿online1 小时前
nvm安装使用,控制node版本
开发语言·前端·学习
web Rookie1 小时前
React 中 createContext 和 useContext 的深度应用与优化实战
前端·javascript·react.js
男孩121 小时前
react高阶组件及hooks
前端·javascript·react.js