React 19 终于来了!全新特性与改进,开发者必备升级指南

游客2024-06-27 12:21:25

早在数月前,React团队便预告了React19的积极开发,并预计上半年发布。4月25日,React总算发布了v19测试版。该版本主要面向各大库,以确保它们与React19的兼容性。为此,建议开发者先升级至最新的稳定版18.3.0,静候React19的即将版发布。

React19带来了众多新特点和改进,除了提高了开发者的使用体验,还进一步优化了React应用的性能。为了让开发者能否平稳过渡到React19,React团队特意打算了一份详细的升级手册,详尽列举了升级步骤和可能碰到的重大变化。

接出来,本文将探讨React18.3与React19的更新内容,并探求React19的升级手册,推动更好地拥抱这一重大更新!

React18.3更新内容

React18.3相对于18.2降低了对废弃API的警告以及其他为React19所需的修改。

ReactDOM

React19更新内容

在React应用中,执行数据变更并据此更新状态是一个常见的需求。例如,用户递交表单以更改姓名时,我们会发起API恳求并处理其响应。往年,我们不得不自动管理挂起状态、错误处理、乐观更新以及次序恳求等逻辑。

比如,可以使用来处理挂起和错误状态:

 

function UpdateName({}) {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, setIsPending] = useState(false);

  const handleSubmit = async () => {
    setIsPending(true);
    const error = await updateName(name);
    setIsPending(false);
    if (error) {
      setError(error);
      return;
    } 
    redirect("/path");
  };

  return (
    
setName(event.target.value)} /> {error &&

{error}

}
); }

 

在React19中降低了对在转换中使用异步函数的支持,以手动处理挂起状态、错误、表单和豁达更新。

比如,可以使用来手动处理挂起状态:

 

function UpdateName({}) {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = async () => {
    startTransition(async () => {
      const error = await updateName(name);
      if (error) {
        setError(error);
        return;
      } 
      redirect("/path");
    })
  };

  return (
    
setName(event.target.value)} /> {error &&

{error}

}
); }

 

异步转换会立即将状态设为true,发起异步恳求,并在转换完成后将设置为false。这样可以确保数据变化时,UI一直保持响应和交互性。

在功能的基础上,React19还推出了Hook来简化豁达更新的管理,以及React.Hook来处理的常见场景。同时,在react-dom中,新增了来手动管理表单操作,并提供了来支持表单中的常见需求。

在React19中,前面的反例可以简化为:

 

function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const error = await updateName(formData.get("name"));
      if (error) {
        return error;
      }
      redirect("/path");
    }
  );

  return (
    
{error &&

{error}

}
); }

 

下边就来瞧瞧这种新的功能的作用和使用方式。

全新Hook:

为了让常见的用例愈发简便,因而添加了一个名为的新Hook:

 

const [error, submitAction, isPending] = useActionState(async (previousState, newName) => {  
  const error = await updateName(newName);  
  
  if (error) {  
    // 可以返回Action的任何结果,这里只返回错误信息。  
    return error;  
  }  
  
  // 处理成功的情况  
});

 

接受一个函数(即“”),并返回一个可调用的包装。这之所以还能工作是由于是可组合的。当调用包装后的时,将返回的最后一个结果作为数据,并将的挂起状态作为返回。

ReactDOM:

在React19中,与react-dom的新特点进行了深度整合。如今,可以将函数作为、和元素的和属性,便于手动使用递交表单:

 

 

当的成功执行时,React会手动为不受控组件重置表单状态。假如须要自动重置,ReactDOMAPI提供了全新的方式。

ReactDOM新Hook:

在建立设计系统时,常常须要创建一些设计组件,这种组件须要才能访问其所在表单的状态信息,而毋须通过层层传递props来实现。即使通过也可以实现这一功能,但为了简化常见场景下的使用,React19引入了一个新的Hook:

 

import { useFormStatus } from 'react-dom';  
  
function DesignButton() {  
  const { pending } = useFormStatus();  
  
  return 

 

可以读取父级的状态,如同该表单是一个一样。

全新Hook:

在数据变更操作中,一种常见的UI模式是在异步恳求执行期间豁达地显示预期的最终状态。React19引入了新的Hook,以简化这一流程:

 

function ChangeName({currentName, onUpdateName}) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName);

  const submitAction = async formData => {
    const newName = formData.get("name");
    setOptimisticName(newName);
    const updatedName = await updateName(newName);
    onUpdateName(updatedName);
  };

  return (
    
      

Your name is: {optimisticName}

); }

 

在这个事例中,Hook准许在恳求还在进行时,立刻将输入框的值(即)设置为,因而豁达地更新UI。假如更新成功,则通过反弹函数来确认状态的修改;假如更新失败或发生错误,可以通过回滚到原始状态。

全新API:use

在React19中,引入了一个全新的API——use,它容许在组件渲染时直接读取资源。

举个反例,可以使用use来读取一个对象,而React将会手动挂起()组件的渲染,直至该解析完成:

 

import { use } from 'react';  
  
function Comments({ commentsPromise }) {  
  // 使用use读取Promise,React会在Promise解析前挂起组件渲染  
  const comments = use(commentsPromise);  
  
  return (  
    
{comments.map((comment) => (

{comment.text}

))}
); } function Page({ commentsPromise }) { // 当Comments组件因use挂起时,这里会显示Suspense的fallback内容 return ( 加载中...
}> ); }

 

同样可以使用use来读取上下文(),因而实现在特定条件下(比如初期返回以后)按需读取上下文数据。

初期返回指的是在函数或方式中的某个点提早结束执行,并返回结果或退出,而不是继续执行剩余的代码。在函数内部,按照个别条件或逻辑,你可能会决定不须要继续执行后续的代码,此时可以使用句子来立刻退出函数。

 

import {use} from 'react';
import ThemeContext from './ThemeContext'

function Heading({children}) {
  if (children == null) {
    return null;
  }
   
  // 使用 useContext 在这里不会生效 ,因为存在早期返回。
  const theme = use(ThemeContext);
  return (
    

{children}

); }

 

useAPI的调用仅限于渲染阶段,与React的Hook类似。但是,与Hook不同的是,useAPI准许在条件句子中灵活调用。展望未来,React团队计划进一步扩充useAPI的功能,提供更多在渲染时消费资源的方法。

React服务器组件服务器组件

服务器组件是一种创新性的技术,它容许在打包前,在独立于顾客端应用程序或服务器端渲染(SSR)服务器的环境中预先渲染组件。这个独立的环境即为React服务器组件中的“服务器”。服务器组件有两种运行模式:一种是在建立时(比如在持续集成服务器上)运行一次;另一种是针对每位恳求,通过Web服务器进行实时运行。

React19全面集成了来自频道的所有服务器组件特点。这意味着,这些带有服务器组件的库如今可以将React19作为对等依赖项进行目标定位,并通过react-导入条件,为支持全栈React构架的框架提供强悍的支持。

服务器操作

服务器动作是一项强悍功能,它容许顾客端组件调用并执行在服务端的异步函数。

通过“use”指令定义服务器操作后,框架将智能地创建一个指向服务端函数的引用,并安全地将该引用传递给顾客端组件。当顾客端组件须要调用这个函数时,React会负责向服务器发送恳求,执行相应的函数,并将结果返回给顾客端。

服务器操作的创建特别灵活,既可以在服务器组件内部创建,并作为属性传递给顾客端组件使用;也可以直接在顾客端组件中导出并调用。这些设计致使服务器操作就能无缝集成到应用中,实现前后端数据的流畅交互。

功能改进ref作为属性

从React19开始,可以将ref作为函数组件的参数进行访问:

 

function MyInput({placeholder, ref}) {  
  return   
}  
  
//...  
  

 

新的函数组件将不再须要,React团队将会发布一个代码转换工具来手动更新组件,以使用新的ref属性。在未来的版本中,将弃用并移除。

水合错误报告

React19在react-dom中对水合错误的报告进行了优化。过去,在开发模式下遇见水合不匹配时,系统常常只记录多个错误,而缺少关于不匹配内容的具体信息。如今,引入了diff功能,致使顾客端渲染与服务端渲染内容之间的差别一目了然。这一改进除了提高了错误报告的清晰度,更有助于开发者迅速定位并修补水合相关问题,因而急剧提高开发效率。

 1

现今只会记录一条包含不匹配内容差别的消息:

 2

作为提供者

React19准许直接将用作提供者,而无需使用传统的写法:

 

const ThemeContext = createContext('');  
  
function App({children}) {  
  return (  
      
      {children}  
      
  );  
}

 

这些新的句型愈发简练直观。为了便捷开发者升级现有代码,React团队将发布一个代码转换工具,才能手动将现有的转换为新的提供者。未来版本中,将逐渐弃用,以促进React社区向愈发简化的句型过渡。

refs清除函数

如今支持从ref反弹函数中返回一个清除函数:

 

 {  
    // 创建 ref  
  
    // 新增:返回一个清理函数,当元素从 DOM 中移除时重置 ref。  
    return () => {  
      // ref 的清理工作  
    };  
  }}  
/>

 

当组件卸载时,React将调用从ref反弹函数中返回的清除函数。这适用于DOMrefs、类组件的refs以及。

因为引入了ref清除函数的机制,如今将拒绝从ref反弹函数中返回除清除函数以外的任何内容。为了防止这个问题,我们一般建议避开使用隐式返回,例如将形参操作置于花括弧中,如下所示:

 

(instance = current)} />

 

 

{ instance = current; }} />

 

这些改变是由于难以判定原始代码中返回的是否应当是清除函数,还是无意中的隐式返回值。通过将形参操作明晰地包裹在花括弧中,确保了ref反弹中不会意外地返回任何值,除非有意为之。

为了手动化这些模式的转换,可以使用no--ref--规则进行代码转换。这将帮助你在升级React版本时更顺畅地处理ref相关的代码。

的初始值

React19为引入了选项,该选项容许指定组件首次渲染时返回的值。

 

function Search({ deferredValue }) {  
  // 在组件首次渲染时,返回 initialValue 作为 value。  
  // 随后,useDeferredValue 会在后台计划一次重渲染,使用 deferredValue 作为新的 value。  
  const value = useDeferredValue(deferredValue, { initialValue: '' });  
  
  return (  
      
  );  
}

 

使用可以确保组件在首次渲染时就能立刻显示一个占位值,而无需等待的异步估算完成。此后,当打算好时,会触发组件的后台重渲染,以显示最新的值。这有助于提高应用的响应性和用户体验。

文档元数据支持

在HTML中,例如、和等文档元数据标签一般放置在区域内。但是,在React应用中,决定什么元数据适用于当前页面的组件可能并不直接坐落渲染的位置,甚至React可能根本不直接渲染。过去,这种元数据标签须要自动通过或利用如react-这样的库进行插入,且在服务器渲染React应用时须要非常当心处理。

React19引入了原生支持在组件中渲染文档元数据标签的功能。这意味着可以直接在组件内部定义这种标签,React会手动将它们提高到文档的部份。这一改进确保了元数据标签才能在仅顾客端应用、流式服务端渲染(SSR)以及服务器组件等场景下正常工作。

下边是一个简单的示例,展示了怎样在React组件中定义并使用这种元数据标签:

 

function BlogPost({ post }) {  
  return (  
    

{post.title}h1> {/* 直接在组件内部定义元数据标签 */} {post.title}title> <meta name="author" content="Josh" /> <link rel="author" href="https://twitter.com/joshcstory/" /> <meta name="keywords" content={post.keywords.join(', ')} /> <p> Eee equals em-see-squared... p> article> ); } </code></pre><p> </p><p>在这个组件中,虽然、和标签被定义在了内部,但React会在渲染时手动将它们移至文档的区域。这些方法简化了元数据的管理,还提高了React应用在各类渲染模式下的兼容性和灵活性。</p><p>式样表支持</p><p>在Web开发中,款式表的管理至关重要,无论是通过外部链接()还是内嵌方法()引入,都须要在DOM中精准布局,以确保款式优先级得到妥善处理。但是,建立一个才能支持组件内部款式表组合的机制常常非常冗长,因而开发者经常面临权衡:要么将款式远离其依赖的组件加载,牺牲组织性;要么依赖外部款式库,降低额外的复杂性。</p><p>React19针对这一挑战提供了外置支持,除了简化了款式表的管理流程,还进一步提高了与顾客端并发渲染和服务器端流式渲染的集成能力。通过指定款式表的优先级,React将手动处理款式表在DOM中的插入次序,确保在展示依赖于特定款式规则的内容之前,相关的款式表(无论外部还是内嵌)早已被加载并应用。</p><p> </p><pre><code class="language-plaintext">function ComponentOne() { return ( <Suspense fallback="loading..."> <link rel="stylesheet" href="foo" precedence="default" /> <link rel="stylesheet" href="bar" precedence="high" /> <article class="foo-class bar-class"> {...} article> Suspense> ) } function ComponentTwo() { return ( <div> <p>{...}p> <link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar div> ) } </code></pre><p> </p><p>在服务端渲染过程中,React会将款式表包含在标签中,以确保浏览器在加载完成前不会进行页面勾画。假如在早已开始流式传输后才发觉款式表,React将确保在顾客端将款式表插入到标签中,之后再展示依赖于该款式表的边界的内容。</p><p>在顾客端渲染过程中,React会等待新渲染的款式表加载完成后再递交渲染结果。假如在应用中的多个位置渲染了这个组件,React将确保款式表在文档中只被包含一次。</p><p> </p><pre><code class="language-plaintext">function App() { return <> <ComponentOne /> ... <ComponentOne /> // 不会导致 DOM 中出现重复的样式表链接 > } </code></pre><p> </p><p>对于这些习惯于自动加载款式表的开发者来说,React19的这一改进为她们提供了一个便利的机会。如今,可以将款式表直接置于依赖它们的组件后面,这除了有助于提高代码的可读性和可维护性,致使开发者可以愈发清晰地了解每位组件的款式依赖关系,并且还才能确保只加载真正须要的款式表。</p><p>据悉,款式库和与打包器集成的款式工具也可以采用这一新特点。虽然开发者不直接渲染自己的款式表,只要她们所使用的工具升级到支持这一功能,她们同样还能享受到这一改进带来的益处。</p> </article> <div class="mimiwuqi pagebar-2"> </div> <div class="doc"> <div class="btn"><a href="https://www.mimiwuqi.com/e/dongpo/doc/?classid=33&id=195165" target="_blank">《React 19 终于来了!全新特性与改进,开发者必备升级指南》文档下载.doc</a></div> </div> <div class="mimiwuqi art-copyright"> <p>本文是由用户"游客"发布,所有内容的版权归原作者所有。没有经过书面许可,任何单位或个人不得以任何形式复制、转载、引用本网站的内容。否则将追究法律责任。<br></p> </div> </div> <div class="mimiwuqi prev-next"> <p class="mimiwuqi art-prev"> <a href="https://www.mimiwuqi.com/kuangjia/195159.html" rel="prev"><span>上一篇</span> 使用 Axios 在 React 中创建实时搜索功能 </a> </p> <p class="mimiwuqi art-next"> <a href="https://www.mimiwuqi.com/kuangjia/195350.html" rel="next"><span>下一篇</span> 面试官让我回答 React 和 Vue 框架的区别. </a> </p> </div> </div> <div class="mimiwuqi col-right r"> <div class="mimiwuqi widget widgetModule widgetHotPost"> <div class="mimiwuqi com-tit"> <h4 class="mimiwuqi title-2">相关专题</h4> </div> <ul class="mimiwuqi mod-main mod-post"> <li> <a href="https://www.mimiwuqi.com/kuangjia/195402.html" target="_blank" title="Vuejs代码实践中篇" class="mimiwuqi clearfix"> <div> Vuejs代码实践中篇 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195401.html" target="_blank" title="一个开箱即用的中后台前端框架,基于vue3+vite2开发" class="mimiwuqi clearfix"> <div> 一个开箱即用的中后台前端框架,基于vue3+vite2开发 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195400.html" target="_blank" title="vuejs2.0实战:仿豆瓣app项目,创建组件header,tabbar路由跳转" class="mimiwuqi clearfix"> <div> vuejs2.0实战:仿豆瓣app项目,创建组件header,tabbar路由跳转 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195399.html" target="_blank" title="大前端主流框架( Vue3全家桶+NestJS+Vite4+TS4+Mysql8+Nginx)" class="mimiwuqi clearfix"> <div> 大前端主流框架( Vue3全家桶+NestJS+Vite4+TS4+Mysql8+Nginx) </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195398.html" target="_blank" title="推荐三个 Vue 后台管理模版,配合 Spring Boot 使用真香" class="mimiwuqi clearfix"> <div> 推荐三个 Vue 后台管理模版,配合 Spring Boot 使用真香 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195397.html" target="_blank" title="Vue 涉及国家安全漏洞?尤雨溪亲自回应" class="mimiwuqi clearfix"> <div> Vue 涉及国家安全漏洞?尤雨溪亲自回应 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195396.html" target="_blank" title="vuejs源码之虚拟dom中的vnode" class="mimiwuqi clearfix"> <div> vuejs源码之虚拟dom中的vnode </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195395.html" target="_blank" title="vue整体框架和主要流程分析,让你从原理上掌握vue,入木三分" class="mimiwuqi clearfix"> <div> vue整体框架和主要流程分析,让你从原理上掌握vue,入木三分 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195394.html" target="_blank" title="小白教程|一小时上手最流行的前端框架vue" class="mimiwuqi clearfix"> <div> 小白教程|一小时上手最流行的前端框架vue </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195393.html" target="_blank" title="如何在具有自己 UI 的 VueJS 应用程序中使用 SuperTokens" class="mimiwuqi clearfix"> <div> 如何在具有自己 UI 的 VueJS 应用程序中使用 SuperTokens </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195392.html" target="_blank" title="九个很受欢迎的vue前端UI框架" class="mimiwuqi clearfix"> <div> 九个很受欢迎的vue前端UI框架 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195391.html" target="_blank" title="一起学Vue:UI框架(element-ui)" class="mimiwuqi clearfix"> <div> 一起学Vue:UI框架(element-ui) </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195390.html" target="_blank" title="认识 vuejs、vue-cli和webpack" class="mimiwuqi clearfix"> <div> 认识 vuejs、vue-cli和webpack </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195389.html" target="_blank" title="推荐6款Vue管理后台框架,收藏好,留备用" class="mimiwuqi clearfix"> <div> 推荐6款Vue管理后台框架,收藏好,留备用 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195388.html" target="_blank" title="《Vuejs开发技巧》Nuxt项目部署+Nginx端口代理" class="mimiwuqi clearfix"> <div> 《Vuejs开发技巧》Nuxt项目部署+Nginx端口代理 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195387.html" target="_blank" title="Vuetify-广受欢迎的Material风格的开源UI框架" class="mimiwuqi clearfix"> <div> Vuetify-广受欢迎的Material风格的开源UI框架 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195386.html" target="_blank" title="基于VUE生态下的UI框架,你知道几个?" class="mimiwuqi clearfix"> <div> 基于VUE生态下的UI框架,你知道几个? </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195385.html" target="_blank" title="前端三大框架-vuejs之父尤雨溪" class="mimiwuqi clearfix"> <div> 前端三大框架-vuejs之父尤雨溪 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195384.html" target="_blank" title="开源项目vuestic-admin——Vue的后端UI框架" class="mimiwuqi clearfix"> <div> 开源项目vuestic-admin——Vue的后端UI框架 </div> </a> </li> <li> <a href="https://www.mimiwuqi.com/kuangjia/195383.html" target="_blank" title="每日 Vue 开源探索|Vue 实用小工具和框架合集,助力开发效率提升!" class="mimiwuqi clearfix"> <div> 每日 Vue 开源探索|Vue 实用小工具和框架合集,助力开发效率提升! </div> </a> </li> </ul> </div> </div> </div> </div> <footer class="footer"> <div class="footer-bottom"> <div class="container"> <div class="footer-copyright">Copyright © 2009 mimiwuqi.com<span>・ </span><a rel="nofollow" target="__blank" href="https://beian.miit.gov.cn">晋ICP备2021011283号</a> <script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?cadf3e80d4eafd567dbf777757d3c6ad"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })(); </script></div> </div> </div> <div class="foot-link"> <img rel="nofollow" src="https://www.mimiwuqi.com/skin/truste.png" alt="TRUSTe Privacy Certification"> <img rel="nofollow" src="https://www.mimiwuqi.com/skin/icon3.png" alt="可信网站"> <img rel="nofollow" src="https://www.mimiwuqi.com/skin/f1ee261b96ae71e845efba398efeca02.png" alt="网站安全"> <img rel="nofollow" src="https://www.mimiwuqi.com/skin/a6ef792fec0a3f4f370cb8d8c6d11134.png" alt="服务时间"> </div> <div class="rollbar"> <div class="rollbar-item"><a rel="nofollow" target="_blank" title="举报反馈留言" href="https://www.mimiwuqi.com/e/tool/gbook/?bid=2"><i class="iconfont icon-gerenfankui"></i>反馈</a></div> <div class="rollbar-item to_full" etap="to_full" title="全屏页面"><i class="iconfont icon-quanping1"></i></div> <div class="rollbar-item to_top" etap="to_top" title="返回顶部"><i class="iconfont icon-dingbu"></i></div> </div> <div id="loginbox"></div> <script type="text/javascript" src="/skin/images/login.js"></script> </footer> <div class="waveHorizontals mobile-hide"> <div id="waveHorizontal1" class="waveHorizontal"></div> <div id="waveHorizontal2" class="waveHorizontal"></div> <div id="waveHorizontal3" class="waveHorizontal"></div> </div> <div class="mimiwuqi m-mask"></div> <script src="https://www.mimiwuqi.com/skin/mimiwuqi/js/jquery.fancybox.min.js" type="text/javascript"></script> <link rel="stylesheet" href="https://www.mimiwuqi.com/skin/mimiwuqi/css/jquery.fancybox.min.css"> <script src="https://www.mimiwuqi.com/skin/mimiwuqi/js/style.js" type="text/javascript"></script> <script> $(function () { var imgCounter = 1; // 初始化图片计数器 $('.content').find('img').each(function () { var _this = $(this); var _src = _this.attr("src"); var _alt = 'React 19 终于来了!全新特性与改进,开发者必备升级指南 ' + imgCounter; // 添加编号 _this.wrap('<a data-fancybox="images" href="' + _src + '" data-caption="' + _alt + '"></a>'); _this.attr('title', _alt); imgCounter++; // 计数器递增 }); }); </script> </body> </html> </body> </html>