使用Laravel后端构建React应用程序:第2部分,React

本文是使用Laravel构建React应用程序系列的第二部分,主要讲解如何在Laravel环境中设置React,包括使用Laravel 5.5的React Preset命令和手动设置方法。教程还涵盖了开发React应用程序,展示产品数据和添加新产品的步骤,引导读者通过实践理解Laravel和React的结合使用。
最终产品图片
您将要创造的

这是关于使用Laravel后端构建React应用程序的系列的第二部分也是最后一部分。 在本系列的第一部分中,我们使用Laravel为基本的产品列表应用程序创建了RESTful API。 在本教程中,我们将使用React开发前端。

我们还将考虑所有可用的选项,以弥合Laravel和React之间的鸿沟。 您无需遵循本系列的第一部分即可理解本教程。 如果您在这里看到React和Laravel如何相处融洽,那么您实际上可以避免第一部分。 您应该转到GitHub ,克隆存储库,然后快速浏览以下内容以开始使用。

快速回顾

在上一个教程中,我们开发了一个可响应API调用的Laravel应用程序。 我们为简单的产品列表应用程序创建了路线,控制器和模型。 由于返回HTTP请求的响应是控制器的工作,因此视图部分被完全跳过。

然后,我们讨论了使用Laravel进行异常处理和验证的技术。 在教程结束时,我们有了Laravel后端API。 现在,我们可以使用此API为网络和各种移动设备构建应用程序。

在本教程中,我们将把重点转移到前端。 本教程的前半部分是关于在Laravel环境中设置React。 我还将向您介绍Laravel Mix(由Laravel 5.4和更高版本支持),它是用于编译资产的API。 在本教程的后半部分,我们将从头开始构建一个React应用程序。

在Laravel中设置React

Laravel Mix是在Laravel 5.4中引入的,目前是连接React和Laravel的理想方法。 使用Laravel 5.5,整个过程变得更加容易。 我在下面介绍了这两种方法。

使用React Preset命令(Laravel 5.5)

Laravel 5.5具有一项崭新的功能,可让您使用工匠的preset react命令来构建React组件的代码。 在以前的Laravel版本中,在Laravel中设置React并非易事。 如果您正在运行最新版本的Laravel,请运行以下命令将React预设添加到您的项目中。

php artisan preset react

默认情况下,Laravel随Vue预设一起提供,并且以上命令将Vue的所有实例替换为React。 有趣的是,如果您不需要预设,则可以使用php artisan preset none命令将其完全删除。

如果一切顺利,这应该显示在您的终端中。

React scaffolding installed successfully.
Please run "npm install && npm run dev" to compile your fresh scaffolding.

在后台,Laravel使用Laravel Mix,它是Webpack的平滑包装器。 您可能已经知道,Webpack是一个模块捆绑器。 它解决了所有模块依赖性,并为JavaScript和CSS生成了必要的静态资产。 React需要一个模块捆绑器来工作,而webpack恰好适合这个角色。 因此Laravel Mix是位于webpack之上的层,使在Laravel中使用webpack更容易。

如果您以后需要自定义Webpack配置,则更好地了解Laravel Mix的工作方式非常重要。 React预设命令没有提供有关事物在后台如何工作的信息。 因此,让我们删除React预设,然后手动回溯步骤。

手动方法(Laravel 5.4)

如果您正在运行Laravel 5.4,或者只是好奇地了解Laravel Mix的配置方式,请按照以下步骤操作:

使用npm安装reactreact-dombabel-preset-react 。 也安装纱线可能是一个好主意。 Laravel和React比npm更喜欢纱线已经不是什么秘密了。

转到位于Laravel项目根目录内的webpack.mix.js。 这是配置文件,您在其中声明应如何编译资产。 替换行mix.js('resources/assets/js/app.js', 'public/js');mix.react('resources/assets/js/app.js', 'public/js');app.js是我们JavaScript文件的入口点,编译后的文件将位于public / js内 。 在终端中运行npm install以安装所有依赖项。

接下来,转到resources / assets / js 。 已经有一个components文件夹和两个其他JavaScript文件。 React组件将进入components目录。 删除现有的Example.vue文件并为示例React组件创建一个新文件。

资源/资产/js/component/Main.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

/* An example React component */
class Main extends Component {
    render() {
        return (
            <div>
                <h3>All Products</h3>
            </div>
        );
    }
}

export default Main;

/* The if statement is required so as to Render the component on pages that have a div with an ID of "root";  
*/

if (document.getElementById('root')) {
    ReactDOM.render(<Main />, document.getElementById('root'));
}

更新app.js以删除所有与Vue相关的代码,并导入React组件。

资源/资产/js/app.js
require('./bootstrap');

/* Import the Main component */
import Main from './components/Main';

现在,我们只需要使视图可以访问资产。 视图文件位于resources / views目录中。 让我们在welcome.blade.php中添加一个<script>标记,这是导航到localhost:8000/时呈现的默认页面。 删除视图文件的内容,并将其替换为以下代码:

资源/视图/welcome.blade.php

<!doctype html>
<html lang="{{ app()->getLocale() }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel React application</title>
        <link href="{{mix('css/app.css')}}" rel="stylesheet" type="text/css">
    </head>
    <body>
    <h2 style="text-align: center"> Laravel and React application </h2>
        <div id="root"></div>
        <script src="{{mix('js/app.js')}}" ></script>
    </body>
</html>

最后,执行npm run devyarn run dev来编译资产。 如果访问localhost:8000 ,应该看到:

Laravel和React一起运行
在Laravel的视图中嵌入React。

package.json具有监视脚本,该监视脚本在检测到任何更改时自动编译资产。 要启用此模式,请运行npm run watch

恭喜,您已成功配置React与Laravel一起使用。 现在,让我们为前端创建一些React组件。

开发React应用程序

如果您不熟悉React,那么本教程的其余部分会有些挑战。 我建议参加“ 初学者React速成课程”系列,以更好地了解React的概念。 让我们开始吧!

React应用程序是围绕组件构建的。 组件是React中最重要的结构,我们有一个专用于组件的目录。

组件使您可以将UI分成独立的,可重复使用的部分,并单独考虑每个部分。 从概念上讲,组件就像JavaScript函数。 它们接受任意输入(称为“ props”),并返回描述应该在屏幕上显示的内容的React元素。
- 官方React Docs

对于我们正在构建的应用程序,我们将从一个基本组件开始,该组件显示服务器返回的所有产品。 让我们将其命名为Main组件。 该组件最初应注意以下事项:

  • 从API中获取所有产品(GET / api / products)。
  • 以其状态存储产品数据。
  • 显示产品数据。

React不是一个成熟的框架,因此该库本身没有任何AJAX功能。 我将使用fetch() ,这是用于从服务器获取数据的标准JavaScript API。 但是,还有许多替代方法可以对服务器进行AJAX调用。

资源/资产/js/component/Main.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

/* Main Component */
class Main extends Component {

  constructor() {
  
    super();
    //Initialize the state in the constructor
    this.state = {
        products: [],
    }
  }
  /*componentDidMount() is a lifecycle method
   * that gets called after the component is rendered
   */
  componentDidMount() {
    /* fetch API in action */
    fetch('/api/products')
        .then(response => {
            return response.json();
        })
        .then(products => {
            //Fetched product is stored in the state
            this.setState({ products });
        });
  }

 renderProducts() {
    return this.state.products.map(product => {
        return (
            /* When using list you need to specify a key
             * attribute that is unique for each list item
            */
            <li key={product.id} >
                { product.title } 
            </li>      
        );
    })
  }
  
  render() {
   /* Some css code has been removed for brevity */
    return (
        <div>
              <ul>
                { this.renderProducts() }
              </ul> 
            </div> 
      
    );
  }
}

在这里,我们将products的状态初始化为构造函数中的空数组。 组件装入后,我们使用fetch()/ api / products检索产品并将其存储在状态中。 render方法用于描述组件的UI。 所有产品均以列表形式呈现。

React应用程序的屏幕截图-所有产品列表

该页面仅列出了产品标题,这很无聊。 此外,我们还没有任何互动元素。 让我们单击产品标题,然后单击,将呈现有关该产品的更多详细信息。

显示产品数据

以下是我们需要涵盖的内容:

  • 跟踪被点击产品的状态。 我们将其命名为currentProduct ,其初始null
  • 单击产品标题后, this.state.currentProduct将更新。
  • 有关产品的产品详细信息显示在右侧。 在选择产品之前,它会显示“未选择产品”消息。
资源/资产/js/component/Main.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';

/* Main Component */
class Main extends Component {

  constructor() {
  
    super();

    /* currentProduct keeps track of the product currently
     * displayed */
    this.state = {
        products: [],
        currentProduct: null
    }
  }
 
  componentDidMount() {
    //code omitted for brevity
  }

 renderProducts() {
    return this.state.products.map(product => {
        return (
            //this.handleClick() method is invoked onClick.
            <li onClick={
                () =>this.handleClick(product)} key={product.id} >
                { product.title } 
            </li>      
        );
    })
  }
  
   handleClick(product) {
    //handleClick is used to set the state
    this.setState({currentProduct:product});
  
  }
  
  render() {
   /* Some css code has been removed for brevity */
    return (
        <div>
              <ul>
                { this.renderProducts() }
              </ul> 
            </div> 
      
    );
  }
}

在这里,我们将createProduct添加到状态中,并使用null值对其进行了初始化。 单击列表项时, onClick={ () =>this.handleClick(product) }调用handleClick()方法。 handleClick()方法更新currentProduct的状态。

现在要显示产品数据,我们可以在Main组件内部进行渲染,也可以创建一个新组件。 如前所述,将UI拆分为较小的组件是React的工作方式。 因此,我们将创建一个新组件并将其命名为Product。

产品组件嵌套在主组件内部。 主要组件将其状态作为道具传递。 产品组件接受此道具作为输入并呈现相关信息。

资源/资产/js/component/Main.js
render() {
  return (
    /* The extra divs are for the css styles */
        <div>
            <div>
             <h3> All products </h3>
              <ul>
                { this.renderProducts() }
              </ul> 
            </div> 
          
            <Product product={this.state.currentProduct} />
        </div>
    );
  }
}
资源/资产/js/component/Product.js
import React, { Component } from 'react';

/* Stateless component or pure component
 * { product } syntax is the object destructing
 */
const Product = ({product}) => {
   
  const divStyle = {
      /*code omitted for brevity */
  }

  //if the props product is null, return Product doesn't exist
  if(!product) {
    return(<div style={divStyle}>  Product Doesnt exist </div>);
  }
    
  //Else, display the product data
  return(  
    <div style={divStyle}> 
      <h2> {product.title} </h2>
      <p> {product.description} </p>
      <h3> Status {product.availability ? 'Available' : 'Out of stock'} </h3>
      <h3> Price : {product.price} </h3>
     
    </div>
  )
}

export default Product ;

该应用程序现在应如下所示:

显示产品详细信息的React应用程序的屏幕截图

添加新产品

我们已经成功实现了与检索所有产品并显示它们相对应的前端。 接下来,我们需要一个表格以将新产品添加到产品列表中。 添加产品的过程可能比从API中获取数据要复杂得多。

这是我认为开发此功能所需的条件:

  • 一个新的有状态组件,为输入表单呈现UI。 组件的状态保存表单数据。
  • 提交后,子组件使用回调将状态传递给Main组件。
  • Main组件具有一个handleNewProduct()的方法,该方法处理启动POST请求的逻辑。 收到响应后,Main组件将更新其状态( this.state.productsthis.state.currentProduct

听起来不是很复杂,对吗? 让我们一步一步地做。 首先,创建一个新组件。 我将其称为AddProduct

资源/资产/js/component/AddProduct.js
class AddProduct extends Component {

  constructor(props) {
    super(props);
       /* Initialize the state. */
       this.state = {
          newProduct: {
              title: '',
              description: '',
              price: 0,
              availability: 0
          }
        }
    
    //Boilerplate code for binding methods with `this`
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleInput = this.handleInput.bind(this);
  }
  
  /* This method dynamically accepts inputs and stores it in the state */
  handleInput(key, e) {
    
    /*Duplicating and updating the state */
    var state = Object.assign({}, this.state.newProduct); 
    state[key] = e.target.value;
    this.setState({newProduct: state });
  }
 /* This method is invoked when submit button is pressed */
  handleSubmit(e) {
    //preventDefault prevents page reload   
    e.preventDefault();
    /*A call back to the onAdd props. The current
     *state is passed as a param
     */
    this.props.onAdd(this.state.newProduct);
  }

  render() {
    const divStyle = {
        /*Code omitted for brevity */ }
    
    return(
      <div> 
        <h2> Add new product </h2>
        <div style={divStyle}> 
        /*when Submit button is pressed, the control is passed to 
         *handleSubmit method 
         */
        <form onSubmit={this.handleSubmit}>
          <label> Title: 
           { /*On every keystroke, the handeInput method is invoked */ }
            <input type="text" onChange={(e)=>this.handleInput('title',e)} />
          </label>
          
          <label> Description: 
            <input type="text" onChange={(e)=>this.handleInput('description',e)} />
          </label>
          
         { /* Input fields for Price and availability omitted for brevity */}

          <input type="submit" value="Submit" />
        </form>
      </div>
    </div>)
  }
}

export default AddProduct;

该组件基本上呈现输入形式,并且所有输入值都存储在状态( this.state.newProduct )中。 然后,在表单提交时,将调用handleSubmit()方法。 但是AddProduct需要将信息传达回父级,我们使用回调来实现。

作为父项的Main组件将函数引用作为prop传递。 子组件AddProduct在我们的示例中,调用此道具以将状态更改通知父组件。 所以这一行this.props.onAdd(this.state.newProduct); 是通知新产品的父组件的回调示例。

现在,在Main组件内部,我们将声明<AddProduct />如下:

<AddProduct onAdd={this.handleAddProduct} />

onAdd事件处理程序链接到组件的handleAddProduct()方法。 此方法托管用于向服务器发出POST请求的代码。 如果响应指示已成功创建产品,则将更新productscurrentProducts的状态。

handleAddProduct(product) {
     
    product.price = Number(product.price);
    /*Fetch API for post request */
    fetch( 'api/products/', {
        method:'post',
        /* headers are important*/
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        
        body: JSON.stringify(product)
    })
    .then(response => {
        return response.json();
    })
    .then( data => {
        //update the state of products and currentProduct
        this.setState((prevState)=> ({
            products: prevState.products.concat(data),
            currentProduct : data
        }))
    })

  }

不要忘记使用this.handleAddProduct = this.handleAddProduct.bind(this);handleProduct方法绑定到类this.handleAddProduct = this.handleAddProduct.bind(this); 在构造函数中。 这是该应用程序的最终版本:

应用程序最终版本的屏幕截图

接下来是什么?

没有删除和更新功能的应用程序是不完整的。 但是,如果您一直在密切关注本教程,那么您应该可以轻松地填补空白。 为了让您入门,我为您提供了删除和更新方案的事件处理程序逻辑。

删除产品的逻辑
handleDelete() {
     
    const currentProduct = this.state.currentProduct;
    fetch( 'api/products/' + this.state.currentProduct.id, 
        { method: 'delete' })
        .then(response => {
          /* Duplicate the array and filter out the item to be deleted */
          var array = this.state.products.filter(function(item) {
          return item !== currentProduct
        });
     
        this.setState({ products: array, currentProduct: null});

    });
  }
更新现有产品的逻辑
handleUpdate(product) {

    const currentProduct = this.state.currentProduct;
    fetch( 'api/products/' + currentProduct.id, {
        method:'put',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(product)
    })
    .then(response => {
        return response.json();
    })
    .then( data => {
        /* Updating the state */
        var array = this.state.products.filter(function(item) {
          return item !== currentProduct
      })
        this.setState((prevState)=> ({
            products: array.concat(product),
            currentProduct : product
        }))
    }) 
  }

您需要做的就是深入学习,动手做,然后使用上述逻辑完成应用程序。 我会给您一个提示:理想情况下,“删除”按钮应位于“产品”组件内部,而“更新”功能应具有其自己的组件。 我鼓励您接受这一挑战并完成缺少的组件。

摘要

我们从开始就走了很长一段路。 首先,我们使用Laravel框架创建了REST API。 然后,我们讨论了混合Laravel和React的选项。 最后,我们使用React构建了API的前端。

尽管我们主要致力于使用React创建单页应用程序,但是您可以创建安装在视图中特定元素上的小部件或组件。 React非常灵活,因为它是一个很好的库。

在过去的两年中,React越来越受欢迎。 实际上,我们在市场上有许多商品可供购买,审查,实施等。 如果您正在寻找有关React的其他资源,请随时检查

您之前是否尝试过使用Laravel和React进行实验? 你觉得呢?你有没有什么想法? 在评论中与我们分享。

翻译自: https://code.tutsplus.com/tutorials/build-a-react-app-with-laravel-backend-part-2-react--cms-29443

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值