创建react应用程序_如何使用WordPress和React创建现代Web应用

创建react应用程序

by Bret Cameron

通过布雷特·卡梅伦

Want the advantages of a modern React SPA, but need a back-end that feels familiar? In this article, we’ll go through how to set-up WordPress’s REST API, including custom posts types and fields, and how to fetch this data inside React.

想要现代React SPA的优势,但需要一个熟悉的后端? 在本文中,我们将介绍如何设置WordPress的REST API,包括自定义帖子类型和字段,以及如何在React中获取此数据。

Recently, I was working on a React app for a client when they sprung this question on me: ‘Can we use it with WordPress?

最近,当一个客户向我提出这个问题时,我正在为客户开发一个React应用程序:“我们可以在WordPress中使用它吗? '

Since late 2015, the answer to this question has been yes. But the steps necessary to create a working decoupled site may not seem straightforward, especially to those who aren’t familiar with both WordPress and React.

自2015年底以来,这个问题的答案是肯定的。 但是,创建一个工作的去耦站点所需的步骤似乎并不简单,特别是对于不熟悉WordPress React的用户而言。

On my journey to create a working application, I encountered a handful of tricky obstacles, and in this article, I’ll explain how to avoid them. I’ll also share several tips and tricks I learned along the way!

在创建工作应用程序的过程中,遇到了一些棘手的障碍,在本文中,我将说明如何避免这些障碍。 我还将分享我在此过程中学到的一些技巧和窍门!

内容 (Contents)

第1部分:背景信息 (Part 1: Background Information)
第2部分:WordPress (Part 2: WordPress)
第三部分:React (Part 3: React)
React中的一个工作示例 (A Working Example in React)
结论 (Conclusion)

第1部分:背景信息 (Part 1: Background Information)

什么是无头CMS? (What is a Headless CMS?)

In the past, using a CMS like WordPress meant you had to build your frontend using PHP.

过去,使用WordPress之类的CMS意味着您必须使用PHP构建前端。

Now, with a headless CMS, you can build your front end with whatever technologies you like; this is because of the separation of the front-end and the back-end via an API. If you want to create a SPA (single-page application) using React, Angular or Vue, and control the content using a CMS like WordPress, you can!

现在,借助无头CMS,您可以使用自己喜欢的任何技术来构建前端。 这是因为通过API将前端和后端分开。 如果您想使用React,Angular或Vue创建SPA(单页应用程序),并使用WordPress之类的CMS控制内容,则可以!

我应该了解什么? (What Should I Know to Follow Along?)

You’ll get the most out of this article if you have:

如果您具备以下条件,则可以从本文中获得最大收益:

  • some knowledge of how a CMS like WordPress works, a bit of PHP, and an idea about how to set up a basic WordPress project on your computer;

    了解WordPress之类的CMS的工作原理,一些PHP,以及有关如何在计算机上设置基本WordPress项目的想法;
  • an understanding of JavaScript, including ES6+ language features and React class syntax.

    对JavaScript的理解,包括ES6 +语言功能和React类语法。
缩略语 (Key Acronyms)

Programming is full of jargon, but it does make it a lot quicker to discuss some of the concepts in this article. Here’s a quick recap of the terms we’ll be using:

编程充满了行话,但这确实使讨论本文中的某些概念更快了。 以下是我们将要使用的术语的简要概述:

  • CMS — content management system. Think WordPress, Drupal, Joomla, Magneto.

    CMS-内容管理系统。 想想WordPress,Drupal,Joomla和Magneto。

  • SPA — single-page application. Rather than re-loading each page in its entirety, a SPA application loads content dynamically. The fundamental code (HTML, CSS and JavaScript) of the website is loaded just once. Think React, Vue, Angular.

    SPA —单页应用程序。 SPA应用程序不会重新加载整个页面,而是动态加载内容。 网站的基本代码(HTML,CSS和JavaScript)仅加载一次。 想想React,Vue,Angular。

  • API — application programming interface. In simple terms, a series of definitions, that a service provides to allow you to take and use its data. Google Maps has one. Medium has one. And now, every WordPress site comes with an API in-built.

    API-应用程序编程接口。 简而言之,服务提供的一系列定义允许您获取和使用其数据。 Google Maps有一个中有一个 。 现在, 每个WordPress网站都内置了一个API

  • REST — representational state transfer. A style of web architecture based around HTTP’s request methods: GET , PUT , POST and DELETE . WordPress’s in-built API is a REST or “RESTful” API.

    REST-代表性状态转移。 一种基于HTTP请求方法的Web体系结构样式: GETPUTPOSTDELETE 。 WordPress的内置API是REST或“ RESTful” API。

  • HTTP — hypertext transfer protocol. The set of rules used to transfer data over the web. It’s specified at the beginning of URLs as http or https (the secure version).

    HTTP —超文本传输​​协议。 用于通过Web传输数据的一组规则。 在URL的开头将其指定为httphttps (安全版本)。

  • JSON — JavaScript object notation. Though derived from JavaScript, this is a language-independent format for the storage and transfer of data.

    JSON — JavaScript对象表示法。 尽管从JavaScript派生,但它是一种与语言无关的格式,用于存储和传输数据。

In this article, we’re using WordPress as our CMS. That means programming our back-end in PHP and using WordPress’s REST API to deliver JSON data to our frontend.

在本文中,我们使用WordPress作为CMS。 这意味着用PHP对后端进行编程,并使用WordPress的REST API将JSON数据传递到前端。

在哪里可以看到WordPress的JSON数据? (Where Can I See WordPress’s JSON Data?)

Before getting to the good stuff, a quick note about where you can find the JSON data on your WordPress site. Nowadays, every WordPress website has JSON data available (unless the site owner has disabled or restricted access to it). You take a look at the main JSON of a WordPress site by appending /wp-json to the root domain name.

在学习好东西之前请先简要介绍一下您可以在WordPress网站上找到JSON数据的地方。 如今,每个WordPress网站都有可用的JSON数据(除非网站所有者已禁用或限制对其的访问)。 通过将/wp-json附加到根域名,可以了解WordPress网站的主要JSON。

So, for example, you can take a look at the JSON for WordPress.org by visiting https://wordpress.org/wp-json. Or, if you’re running a WordPress site locally, you can see its JSON by following localhost/yoursitename/wp-json.

因此,例如,您可以通过访问https://wordpress.org/wp-json来查看WordPress.org的JSON。 或者,如果您在本地运行WordPress网站,则可以通过遵循localhost/yoursitename/wp-json来查看其JSON。

To access the data for your posts, type localhost/yoursitename/wp-json/wp/v2/posts . For a custom post format, swap in the new format (e.g. movies) instead of posts. What now looks like an unreadable block of text is exactly what will allow us to use WordPress as a headless CMS!

要访问您的帖子的数据,请输入localhost/yoursitename/wp-json/wp/v2/posts 。 对于自定义帖子格式,请交换为新格式(例如movies )而不是posts 。 现在看起来像一块无法阅读的文本正是使我们能够将WordPress用作无头CMS的原因!

第2部分:WordPress (Part 2: WordPress)

To set-up your REST API, most of what you’ll need to do will happen in your functions.php file. I’ll assume you know how to set up a WordPress project and access it using localhost , but if you’d like some help with that, I recommend this article (it’s what I used to get started programming with WordPress).

要设置您的REST API,您需要做的大多数事情都将在functions.php文件中进行。 我假设您知道如何设置WordPress项目并使用localhost访问它,但是如果您希望获得一些帮助,建议您阅读本文 (这是我开始使用WordPress进行编程的内容)。

For most projects, you’ll want to use a custom post type, so let’s begin by setting one up.

对于大多数项目,您将要使用自定义帖子类型,因此让我们从设置一个帖子开始。

添加自定义帖子类型 (Adding a Custom Post Type)

Let’s say our site is about films, and we want a post type called ‘movies’. First, we want to make sure our ‘movies’ post type loads as soon as possible, so we’ll attach it to the init hook, using add_action :

假设我们的网站是关于电影的,我们想要一个称为“电影”的帖子类型。 首先,我们要确保我们的“电影”帖子类型尽快加载,因此我们将使用add_action将其附加到init钩子上:

add_action( 'init', 'movies_post_type' );

I’m using movies_post_type() , but you can call your function whatever you want.

我正在使用movies_post_type() ,但是您可以随意调用函数。

Next, we want to register ‘movies’ as a post type, using the register_post_type() function.

接下来,我们要使用register_post_type()函数将“电影”注册为帖子类型。

The next chunk of code may look overwhelming, but it’s relatively simple: our function takes a lot of in-built arguments to control the functionality of your new post type, and most of them are self-explanatory. We’ll store these arguments in our $args array.

下一部分代码可能看起来不堪重负,但它相对简单:我们的函数需要很多内置参数来控制新文章类型的功能,并且其中大多数是不言自明的。 我们将这些参数存储在$args数组中。

One of our arguments, labels , can take many different arguments of its own, so we split that off into a separate array, $labels , giving us:

我们的参数之一labels可以采用许多不同的参数,因此我们将其拆分为一个单独的数组$labels ,从而得到:

Two of the most important arguments are 'supports' and 'taxomonies' , because these control which of the native post fields will be accessible in our new post type.

两个最重要的参数是'supports''taxomonies' ,因为它们控制着哪些本机帖子字段可以在我们的新帖子类型中访问。

In the above code, we’ve opted for just three 'supports':

在上面的代码中,我们仅选择了三个'supports'

  • 'title'— the title of each post.

    'title'每个帖子的标题。

  • 'editor'— the primary text editor, which we’ll use for our description.

    'editor'主要的文本编辑器,我们将使用它进行描述。

  • 'thumbnail'— the post’s featured image.

    'thumbnail' -帖子的精选图片。

To see the full list of what’s available, click here for supports, and here for taxonomies.

要查看可用功能的完整列表,请单击此处获取支持 ,并单击此处获取分类法

Generate WordPress also has a handy tool to help you code custom post types, which can make the process a lot quicker.

生成WordPress还提供了一个方便的工具来帮助您编写自定义帖子类型 ,这可以使流程更快。

更改标题占位符文本 (Changing Title Placeholder Text)

If the title placeholder text “enter title here” could be a little misleading for your custom post type, you can edit this in a separate function:

如果标题占位符文本“在此处输入标题”可能会误导您的自定义帖子类型,则可以在单独的函数中对其进行编辑:

将自定义字段添加到您的自定义帖子类型 (Adding a Custom Field to Your Custom Post Type)

What if you want a field that doesn’t come pre-defined by WordPress? For example, let’s say we want a special field called “Genre”. In that case, you’ll need to use add_meta_boxes() .

如果您想要WordPress未预先定义的字段怎么办? 例如,假设我们想要一个名为“流派”的特殊字段。 在这种情况下,您需要使用add_meta_boxes()

For, we need to attach a new function to WordPress’s add_meta_boxes hook:

为此,我们需要将新功能附加到WordPress的add_meta_boxes挂钩:

add_action( 'add_meta_boxes', 'genre_meta_box' );

Inside our new function, we need to call WordPress’s add_meta_box() function, like so:

在我们的新函数中,我们需要调用WordPress的add_meta_box()函数,如下所示:

function genre_meta_box() {  add_meta_box(    'global-notice',    __( 'Genre', 'sitepoint' ),    'genre_meta_box_callback',    'movies',    'side',    'low'  );}

You can read more about this function’s arguments here. For our purposes, the most critical part is the callback function, which we’ve named genre_meta_box_callback . This defines the actual contents on the meta box. We only need a simple text input, so we can use:

您可以在此处阅读有关此函数参数的更多信息。 就我们的目的而言,最关键的部分是回调函数,我们将其命名为genre_meta_box_callback 。 这定义了元框上的实际内容。 我们只需要一个简单的文本输入,因此我们可以使用:

function genre_meta_box_callback() {  global $post;  $custom = get_post_custom($post->ID);  $genre = $custom["genre"][0];  ?>  <input style="width:100%" name="genre" value="<?php   echo $genre; ?>" />  <?php};

Finally, our custom field won’t save its value unless we tell it to. For this purpose, we can define a new function save_genre() and attach it to WordPress’s save_post hook:

最后,除非告知我们,否则我们的自定义字段不会保存其值。 为此,我们可以定义一个新函数save_genre()并将其附加到WordPress的save_post钩子上:

function save_genre(){  global $post;  update_post_meta($post->ID, "printer_category",   $_POST["printer_category"]);};
add_action( 'save_post', 'save_genre' );

Together, the code used to create the custom field should look something like this:

总之,用于创建自定义字段的代码应如下所示:

使自定义字段可作为JSON使用 (Making Custom Fields Available as JSON)

Our custom posts are automatically available as JSON. For our “movies” post type, our JSON data can be found at localhost/yoursitename/wp-json/wp/v2/movies .

我们的自定义帖子将自动以JSON形式提供。 对于我们的“电影”帖子类型,可以在localhost/yoursitename/wp-json/wp/v2/movies找到我们的JSON数据。

However our custom fields are not automatically part of this, and so we need to add a function to make sure they are also accessible via the REST API.

但是,我们的自定义字段不会自动包含在其中,因此我们需要添加一个函数以确保也可以通过REST API访问它们。

First, we’ll need to attach a new function to the rest_api_init hook:

首先,我们需要在rest_api_init钩子上附加一个新函数:

add_action( 'rest_api_init', 'register_genre_as_rest_field' );

Then, we can use the in-built register_rest_field() function, like so:

然后,我们可以使用内置的register_rest_field()函数,如下所示:

function register_genre_as_rest_field() {  register_rest_field(    'movies',    'genre',    array(      'get_callback' => 'get_genre_meta_field',      'update_callback' => null,      'schema' => null,    )  );};

This function takes an array with get and update callback. For a more straightforward use-case like this, we should only need to specify a 'get_callback' :

此函数采用带有getupdate回调的数组。 对于这样更简单的用例,我们只需要指定一个'get_callback'

function get_genre_meta_field( $object, $field_name, $value ) {  return get_post_meta($object['id'])[$field_name][0];};

As a whole, here is the code necessary to register a custom field.

总体而言,这是注册自定义字段所必需的代码。

Out-of-the-box, WordPress’s REST API doesn’t include URL for your featured images. To make it easier to access this, you can use the following code:

开箱即用的WordPress REST API不包含特色图片的URL。 为了使其更易于访问,可以使用以下代码:

The WordPress filter rest_prepare_posts is dynamic, so we can swap in our custom post type in place of “posts”, such as rest_prepare_movies .

WordPress过滤器rest_prepare_posts是动态的,因此我们可以替换自定义帖子类型来代替“帖子”,例如rest_prepare_movies

限制可见的JSON数据 (Restricting Visible JSON Data)

We almost ready to start pulling in data to our React app, but there’s one more quick optimisation we can make, by limiting the data that is made available.

我们几乎准备开始将数据导入我们的React应用程序,但是我们可以通过限制可用数据来进行另一项快速优化。

Some data comes as standard which you may never need in your frontend and — if that’s the case — we can remove it using a filter, like this one. You can find the names of the data types by looking at your /wp-json/wp/v2/movies part of your website.

一些数据是标准的,您可能不需要在前端中使用,如果是这样的话,我们可以使用像这样的过滤器将其删除。 您可以通过查看网站的/wp-json/wp/v2/movies部分找到数据类型的名称。

With that done, once you’ve added a few movies using the WordPress backend, and we have everything we need to start bringing the data into React!

完成之后,一旦您使用WordPress后端添加了几部电影,我们就具备了将数据导入React所需的一切!

第三部分:React (Part 3: React)

To fetch external data in JavaScript, you need to use promises. This will likely have implications for the way you want to structure your React components, and in my case (converting an existing React project), I had to re-write a fair amount of code.

要在JavaScript中获取外部数据,您需要使用Promise。 这可能会影响您想要构建React组件的方式,在我的情况下(转换现有的React项目),我不得不重新编写大量代码。

JavaScript中的承诺 (Promises in JavaScript)

Promises in JavaScript are used to handle asynchronous actions — things that happen outside the usual step-by-step or “synchronous” order of execution (after hoisting).

JavaScript中的承诺用于处理异步动作-发生在通常的逐步执行或“同步”执行顺序之外(在提升之后)的事情。

The good news is that asynchronous JavaScript is a lot easier than it used to be. Before ES6, we were dependent on callback functions. If multiple callbacks were necessary (and they often were), nesting would lead to code that was very difficult to read, scale and debug — a phenomenon sometimes known as callback hell, or the pyramid of doom!

好消息是,异步JavaScript比以前容易得多。 在ES6之前,我们依赖于回调函数。 如果需要多个回调(并且经常是),嵌套将导致代码难以阅读,扩展和调试-这种现象有时被称为回调地狱或末日金字塔!

Promises were introduced in ES6 (or ES2015) to solve that problem, and ES8 (or ES2018) saw the introduction of async ... await , two keywords which further simplify asynchronous functionality. But for our purposes, the most critical promise-based method is fetch() .

在ES6(或ES2015)中引入了承诺来解决该问题,在ES8(或ES2018)中引入了async ... await ,这两个关键字进一步简化了异步功能。 但是出于我们的目的,最关键的基于promise的方法是fetch()

提取方法 (The Fetch Method)

This method has been available since Chrome 40, and it is an easier-to-use alternative to XMLHttpRequest() .

此方法自Chrome 40开始提供,它是XMLHttpRequest()一种易于使用的替代方法。

fetch() returns a promise and so it is “then-able”, meaning that you can use the then() method to process the outcome.

fetch()返回一个promise,因此它是“ then-able”的,这意味着您可以使用then()方法来处理结果。

You can add fetch to a method inside your React class component, like so:

您可以将fetch添加到React类组件内部的方法中,如下所示:

fetchPostData() {  fetch(`http://localhost/yoursitename/wp-json/wp/v2/movies?per_page=100`)  .then(response => response.json())  .then(myJSON => {  // Logic goes here});}

In the code above, two things are important:

在上面的代码中,有两点很重要:

  • First, we are calling a URL with the filter ?per_page=100 appended onto the end. By default, WordPress only shows 10 items per page, and I often find myself wanting to increase that limit.

    首先,我们调用一个URL,并在其末尾附加过滤器?per_page=100 。 默认情况下,WordPress每页仅显示10个项目,我经常发现自己想增加该限制。

  • Second, before processing our data, we are using the .json() method. This method is used primarily in relation to fetch(), and it returns the data as a promise and parses the body text as JSON.

    其次,在处理数据之前,我们使用.json()方法。 此方法主要与fetch() ,它以promise返回数据并将正文文本解析为JSON。

In most cases, we’ll want to run this function as soon as our React component has mounted, and we can specify this using the componentDidMount() method:

在大多数情况下,我们将希望在安装React组件后立即运行此函数,并且可以使用componentDidMount()方法进行指定:

componentDidMount() {  this.fetchPostData();}
处理承诺 (Handling Promises)

Once you have returned a promise, you have to be careful about handling it in the correct context.

退还承诺后,必须谨慎处理正确的上下文。

When I first tried to use promises, I spent a while trying to pass that data to variables outside of the scope of the promise. Here are a few rules of thumb:

当我第一次尝试使用Promise时,我花了一段时间尝试将数据传递给Promise范围之外的变量。 以下是一些经验法则:

  • In React, the best way to use promises is via the state. You can use this.setState() to pass promise data into your component’s state.

    在React中,使用诺言的最好方法是通过状态。 您可以使用this.setState()将承诺数据传递到组件的状态。

  • It is best to process, sort and re-arrange your data within a series of then() methods following the initial fetch() . Once any processing is complete, it is best practice to add the data to state within your final then() method.

    最好在初始fetch() then()在一系列then()方法中处理,排序和重新安排数据。 一旦完成任何处理,最佳实践就是将数据添加到最终的then()方法中的状态。

  • If you want to call any additional functions to process your promise (including within render()) it’s good practice to prevent the function from running until the promise has resolved.

    如果要调用任何其他函数来处理您的Promise(包括在render() ),则最好在该Promise解析之前阻止该函数运行。

  • So, for example, if you’re passing your promise to this.state.data , you can include a conditional within the body of any functions that depend on it, like below. This can prevent annoying unwanted behaviour!

    因此,例如,如果您将承诺传递给this.state.data ,则可以在任何依赖于该条件的函数体内包括一个条件,如下所示。 这样可以防止令人讨厌的不良行为!

myPromiseMethod() {  if (this.state.data) {    // process promise here   } else {    // what to do before the fetch is successful  }}

React中的一个工作示例 (A Working Example in React)

Let’s say we want to pull in the name, description, featured_image and genre of the custom WordPress post type we defined in part 1.

假设我们要提取第1部分中定义的自定义WordPress帖子类型的namedescriptionfeatured_imagegenre

In the following example, we’ll fetch those four elements for each movie and render them.

在下面的示例中,我们将为每部电影获取这四个元素并进行渲染。

As so often with React tutorials, the following block of code may look intimidating, but I hope it will seem much simpler when we break it down.

与React教程一样,下面的代码块可能看起来令人生畏,但我希望当我们分解代码时,它看起来会更简单。

构造函数(道具) (constructor(props))

In this method, we call super(props), define our initial state (an empty data object) and bind three new methods:

在此方法中,我们调用super(props) ,定义初始状态(一个空data对象)并绑定三个新方法:

  • fetchPostData()

    fetchPostData()

  • renderMovies()

    renderMovies()

  • populatePageAfterFetch()

    populatePageAfterFetch()

componentDidMount() (componentDidMount())

We want to fetch our data as soon as the component has mounted, so we’ll call fetchPostData() in here.

我们希望在组件挂载后立即获取数据,因此在此处调用fetchPostData()

fetchPostData() (fetchPostData())

We fetch the JSON from our URL, passing .json() in the first .then() method.

我们从URL中获取JSON,并在第一个.then()方法中传递.json()

In the second .then() method, we extract the four values we want for every movie entry we’ve fetched and then add them to our newState object.

在第二个.then()方法中,我们为获取的每个电影条目提取想要的四个值,然后将它们添加到我们的newState对象中。

We then use this.setState(newState) to add this information to this.state.data .

然后,我们使用this.setState(newState)将此信息添加到this.state.data

renderMovies() (renderMovies())

The conditional if (this.state.data) means that the function will only run once data has been fetched.

条件if (this.state.data)意味着该函数仅在获取数据后才运行。

In here, we take an array of all our fetched movies from this.state.data and pass it to the function populatePageAfterFetch() .

在这里,我们从this.state.data获取所有提取的电影的数组,并将其传递给函数populatePageAfterFetch()

populatePageAfterFetch() (populatePageAfterFetch())

In this function, we prepare the data for each movie to be rendered. This should look straightforward to anyone who’s used JSX, with one potential stumbling block.

在此功能中,我们为每个要渲染的电影准备数据。 对于使用JSX的任何人来说,这看起来都很简单,而且有一个潜在的绊脚石。

The value of movie.description is not plain text, but HTML markup. To display this, we can use dangerouslySetInnerHTML={{__html: movie.description}} .

movie.description的值不是纯文本,而是HTML标记。 为了显示这一点,我们可以使用dangerouslySetInnerHTML={{__html: movie.description}}

Note: The reason this is potentially “dangerous” is that, if your data were hijacked to contain malicious XSS scripts, these would be parsed too. As we’re using our own server/CMS in this article, we shouldn’t need to worry. But if you do want to sanitise your HTML, take a look at DOMPurify.

注意: 这有潜在“危险”的原因是,如果您的数据被劫持以包含恶意XSS脚本,则这些脚本也会被解析。 由于本文中使用的是我们自己的服务器/ CMS,因此我们不必担心。 但是,如果您想清理HTML,请看一下DOMPurify

render() (render())

Finally, we control where our rendered data will appear by calling the renderMovies() method within our chosen <div> tags. We’ve now successfully fetched data from our WordPress site and displayed it!

最后,我们通过在选择的<d iv>标签中调用renderMovies()方法来控制渲染数据的显示位置。 现在,我们已经成功地从WordPress网站获取了数据并显示了数据!

结论 (Conclusion)

Overall, I hope this article makes the process of connecting a React front-end to a WordPress back-end as painless as possible.

总的来说,我希望本文能够尽可能简化将React前端连接到WordPress后端的过程。

Like so much in programming, what can look intimidating to begin with quickly becomes second nature with practice!

就像编程中的许多事情一样,从快速开始看似令人生畏的内容成为实践的第二天性!

I’d be very interested to hear about your own experiences using WordPress as a headless CMS, and I’m happy to answer any questions in the comments.

我很高兴听到您使用WordPress作为无头CMS的经历,我很乐意回答评论中的任何问题。

翻译自: https://www.freecodecamp.org/news/wordpress-react-how-to-create-a-modern-web-app-using-wordpress-ef6cc6be0cd0/

创建react应用程序

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值