Python开发者的React指南

2,835 阅读16分钟

尽管创建用户界面的最流行的JavaScript库是React,但作为Python开发者,学习如何使用这个库可能是一个漫长而艰难的过程。

虽然你可以观看和阅读所有可能的React教程来了解这个库,但如果你不知道适当的路径或逐步的方法,它可能是令人生畏的。

这是因为React使用的语法和数据结构与Python非常不同,这使得Python开发人员难以适应。

在这篇文章中,我们将描绘出一个开始使用React的道路计划,以及作为Python开发者潜入React的核心前提条件。我们还将以Flask(一个Python Web框架)和React为例,创建一个联系人管理应用程序。

在开始之前,你应该有HTML、CSS、JavaScript和Python方面的知识。

React的介绍

Facebook创建并维护用于设计用户界面的React JavaScript库。近年来,由于它能够将反应式和声明式编程的力量带入前端开发领域,因此它越来越受欢迎。

React还使考虑用户界面代码变得更加容易,其编程范式鼓励模块化和可重用的代码。

关键是要理解React不外乎是JavaScript。

React不是一种独立的编程语言,也不是一个需要多年才能理解的特定领域框架。它有一个简单的API,只有几个函数和概念需要掌握,然后你才能用它来创建网络应用。

让我们来了解一些React的概念,这些概念在你用React构建Web应用的过程中是很重要的。

React组件

组件是可重复使用的代码块,自成一体。它们完成与JavaScript函数相同的事情,只是它们独立行动并返回HTML。

它们可以与Python的面向对象编程(OOP)相关,因为Python和JavaScript都展示了数据模型的继承。

然而,在OOP中,数据不受限制,因为任何对象都可以从Python类中创建出来,而不像React组件,数据只限于其他组件的集合。另外,React组件可以为自己的状态保存数据,这一点对于Python类来说是不一样的。

在React中,组件可以将一个网络应用分成具有不同功能的独立部分。例如,你可以为网络应用的标题创建一个组件,为导航菜单创建另一个组件,在应用的其他页面上重复使用它。

组件结构

在Python中,对于如何使用类,没有特殊的层次结构。然而,React组件在一个特定的层次结构中工作。

我们知道,我们可以在其他组件中调用和渲染组件。该组件被称为子组件,而调用子组件的则被称为父组件。这被称为父-子关系。

在本文的后面,你会了解到知道这种架构对组件之间的数据传输有多重要。

类组件

类组件是一个继承了React.Component class 的功能的组件。

下面是一个类组件的例子。

class Header extends React.Component {
  render() {
    return <h2>Hello, I am a header!</h2>;
  }
}

在上面的类组件中,我们包含了extends React.Component 。这句话给该组件增加了一个React.Component 继承,使其可以访问React.Component 功能。

该组件还需要render() 函数,该函数返回HTML,以渲染其中的数据。

在Python中,类组件的等价物如下。

class MyComponent(Component):
    def __init__(self):
        super().__init__()

    def render(self):
        return "Hello, I am a heder" ;

功能性组件

功能性组件和类组件一样,返回HTML,操作也类似,但功能性组件可以用少得多的代码构建,由于其简单的语法,更容易掌握,在本教程中更受青睐。

function Header() {
  return <h2>Hello, I am a header!</h2>;
}

下面是Python的等价物。

def Header() {
  return "Hello, I am a header!";
}

JSX

尽管<h1><div> 标签看起来与HTML标签相同,但它们不是。JSX是对JavaScript的语法扩展,包含这些标签,由React团队设计,以便在JavaScript组件中实现类似HTML的内联标记。

这些都类似于Python的Jinja模板引擎。

JSX和HTML标签之间有几个关键区别。首先是简单地将 **class**关键字现在是className

其次,在HTML中,我们使用像下面这样的字符串来定义内联样式。

<h1 style="color: hotpink; font-size: 12px">Hello<h1>

然而,在JJSX中,我们利用骆驼式的对象。

<h1 style="color": "hotpink", "fontSize": "12px"> Hello </h1>

最后,可以在我们的JJSX标记中添加变量,通过包裹变量来进行渲染。

render() {
    var myText = 'Hello!';
    return (
          <h1>{myText}</h1>
    );
}

除了像<h1><div> 这样的HTML元素,还可以引用其他React类。在我们的src/demo/App.js ,例如,我们通过访问<ExampleComponent> ,渲染ExampleComponent 组件。

样式化组件

有三种方法来设计React组件的样式:使用普通的CSS,使用JavaScript风格的对象的内联样式,或者创建风格化的组件。

使用普通的CSS进行样式设计

在给React组件设置样式的第一种方法中,即使用普通CSS,你必须创建一个普通的CSS文件并将其导入到你的React组件中。导入后,你必须为其对应的HTML或JSX元素的样式添加类名。

下面是一个CSS标题样式的例子。

.header {
  padding: 60px;
  text-align: center;
  background: #1abc9c;
  color: white;
  font-size: 30px;
}

然后我们就有了页眉组件。

import React from 'react';
import PropTypes from 'prop-types';
import './Header.css';
...
export default function Alert() {
  return(
    <div className="header">
      <h2>React Header</h2>
    </div>
  )
}
使用JavaScript风格的对象

在第二种方法中,你必须删除导入的CSS文件,并创建一个对象,其padding为20 ,并使用style属性将该对象传递给div。

import React from 'react';

function Header() {
  const wrapper = {
    padding: 20
  };

  return(
    <div style={wrapper}>
      <h1
    Header.
      </h1>
    </div>
  )
}

export default Header;

值得注意的是,你不需要提供像素作为padding单位。默认情况下,React会将其转换为一个像素的字符串。如果你想要一个特定的单位,就把它写成一个字符串。因此,举例来说,如果你希望padding是一个百分比,那就写成padding: `20%`

使用风格化的组件

第三种方法是通过创建styled-components来设计你的组件。在这种方法中,你必须创建风格化的对象,附加它们,并包裹你的JSX元素。

Styled-components是一个用于React和React Native的开发包。它允许你在你的应用程序中使用组件级的样式,它们使用一种被称为CSS-in-JS的技术将JavaScript与CSS整合起来。

风格化组件建立在标记的模板字面上,这意味着在给组件设计风格时,实际的CSS代码是写在反斜线之间的。因此,开发者可以从一个项目到下一个项目重复使用他们的CSS代码。

当利用风格化组件时,不需要将你的内置组件映射到外部CSS样式。

你可以使用下面的npm命令来安装styled-components。

npm i styled-components@4.1.3

下面是一个如何在我们的React代码中使用它们的例子。

import React from 'react';
import styled from 'styled-components';

// Button component that'll render an <a> tag with some styles
const Button = styled.a`
  background-colour: teal;
  color: white;
  padding: 1rem 2rem;
`
const App = () => {
  return (
    <Button>I am a button</Button>
  )
}
export default App;

你会看到,在构建React功能组件时,你可以用变量类型和它的名字来指定组件的名称,如 const Button.

我们在上面导入了styled ,它为我们提供了styled-components的功能。另外,styled 后面的a 标志着锚定的HTML元素,<a> 。当声明一个风格化组件时,你可以使用任何HTML元素,如<div><h1> 、或<p>

状态

一个状态对象被集成到React组件中。状态对象是你保持组件的属性值的地方,当状态对象发生变化时,组件就会重新提交。

import React, {useState} from "react";
 
const MyComponent = () => {
    const [value, setValue] = useState(1);
    return (
        <div>
            <p>{value}</p>
            <button onClick={() => setValue((value + 1))}>Increment Value</button>
        </div>
    );
};

在上面的代码例子中,我们创建了一个状态value ,它携带的值是1,setValue ,它设置和更新状态value

为了做到这一点,我们使用useState 钩子和setValue 来更新value ,在每次点击按钮时将1 加入其中。然后这个状态会在React DOM中更新,这意味着页面不需要重新加载就能呈现变化。

钩子

钩子是允许你 "钩 "到React功能的函数,如状态和生命周期函数。它们类似于Python中的装饰器,允许你钩住一个类或函数并控制其行为。

要使用钩子,你必须从React库中导入它们。它们不能与类组件一起使用,只能在声明函数属性的组件顶层使用,如下面的代码所示。

import React, { useState } from "react";

function FavoriteColor() {
  const [color, setColor] = useState("red");

  return (
    <>
      <h1>My favorite color is {color}!</h1>
    </>
  );
}

从上面的代码中可以看出,useState Hook被用在功能组件的顶部,也就是在返回语句之前。

Hooks使管理状态更容易,因为它们被内置为你执行简单的状态管理逻辑,这就避免了你在重新发明轮子上浪费时间。

Hooks的一些例子是useStateuseEffect

Props

React道具类似于JavaScript函数参数和HTML属性。它们使用与HTML属性相同的语法来向组件传递道具。

<Header title="My react header" />;

道具是你在组件之间传递数据的方式,可以从一个父组件传递到一个子组件。

function House(props) {
  return <h2>I am a { props.type }!</h2>;
}

function Room() {
  return (
    <>
      <h1>Do you know my room number?</h1>
      <House type="duplex" />
    </>
  );
}

这里的父组件是Room ,而House 是子组件。这是因为House 组件在Room 组件中被调用,这意味着一个道具type 在它们之间被传递。

将状态作为道具传递

你也可以将状态作为道具在父组件和子组件之间传递。

<

function House(props) {
  return <h2>I am a { props.type }!</h2>;
}

function Room() {
  const [type, setType] = useState("Duplex");

  return (
    <>
      <h1>Do you know my room number?</h1>
      <House type={type} setType={setType} />
    </>
  );
}

在上面的代码例子中,我们声明了状态type ,它携带了值"Duplex" ,以及setType ,它更新了状态。

然后我们可以把这些状态作为props传递给House 组件。我们还向House 组件添加了props参数,它收集了已经传递的状态。最后,我们使用props.type 来渲染状态中的数据。

Redux

在React中工作时,你将需要在一个复杂的应用程序中处理跨组件的状态。这个问题由Redux解决,它是一个帮助维护应用程序状态的JavaScript包。Redux将你所有的状态存储在一个单一的源中,你可以在本教程中了解更多关于它的信息

WebPack

Webpack是一个Javascript模块捆绑器,它允许你在你的项目中把依赖关系作为静态文件保存,这样你就不必这样做。装载器也包括在Webpack中,以帮助在你的项目中执行某些活动

服务器渲染

学习服务器渲染将使你能够在服务器上开发组件,并在浏览器中把它们渲染成HTML;在所有的JavaScript模块下载之后,React将登台亮相。

这是React最好的功能之一,它可以与任何后端技术一起利用。你可以在这篇文章中了解服务器渲染的情况

构建Flask和React应用程序

现在让我们建立Flask应用程序来管理数据库和数据请求,这将是我们React应用程序的后端。本节将演示如何建立一个与React一起工作的Python API,然后建立一个React应用程序,从你的IT中发出请求。

Flask的安装

要安装Flask,运行下面的命令。

pip install flask

接下来,运行下面的命令来创建Flask项目。

# create project directory
mkdir flaskapp

cd flaskapp

我们现在可以创建一个app.py ,添加下面的Python代码。

from flask import Flask

app = Flask(__name__)

@app.route('/ping')
def ping():
    return ("hello world")


if __name__ == '__main__':
    app.run()

设置Flask应用程序

Flask应用程序将需要你使用下面的命令安装flask_sqlalchemyflask_cors

pip install flask_sqlalchemy 
pip install flask_cors

安装完所需的模块后,导入它们并进行设置。要做到这一点,在你的app.py 文件中复制下面的代码。

import os
from flask import *
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy

file_path = os.path.abspath(os.getcwd())+"\database.db"
 

app = Flask(__name__)

app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+file_path
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy()
db.init_app(app)

@app.after_request
def after_request(response):
  response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
  response.headers.add('Access-Control-Allow-Credentials', 'true')
  return response

接下来,为联系人管理器设置你的数据库。

class ContactModel(db.Model):
    __tablename__ = "table"
 
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(), unique=True)
 
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return f"{self.name}"

@app.before_first_request
def create_table():
    db.create_all()

ContactManager 模型创建数据库表,@app.before_first_request 装饰器运行建立表的命令,在你第一次使用Flask应用程序时触发。

构建端点

在下面的代码中,我们可以为联系人管理器应用程序创建、检索和更改端点。

@app.route('/data/create' , methods = ['GET','POST'])
def create():
    if request.method == 'GET':
        return jsonify({"success": True, "message": "this is the create endpoint"}), 201
 
    if request.method == 'POST':
        request_data = json.loads(request.data)
        name = request_data['name']
        contact = ContactModel(name=name)
        db.session.add(contact)
        db.session.commit()
        return jsonify({"success": True, "message": "contact added successfully"}), 201

def contact_serializer(contact):
    return {'name': contact.name}

@app.route('/data')
def retrieveDataList():
    return jsonify([*map(contact_serializer, ContactModel.query.all())])


@app.route('/data/delete', methods=['GET','POST'])
def delete():
    request_data = json.loads(request.data)
    name = request_data['name']
    contact = ContactModel.query.filter_by(name=name).first()
    if request.method == 'POST':
        if contact:
            db.session.delete(contact)
            db.session.commit()
            return jsonify({"success": True, "message": "Contact deleted successfully"}), 201
        abort(404)
 
    return jsonify({"success": True}), 201

create 端点在POST 请求下收集数据,以在数据库中创建一个联系人,retrieve 端点获得存储在数据库中的所有数据。

最后,delete 端点在一个POST 请求下接收数据。在进行删除之前,确保检查这些数据是否存在于数据库中。这三个端点在构建你的React应用程序时将非常有用。

构建React应用程序

要开始构建我们的React应用程序,我们必须首先安装React。

npm install react react-dom --save

要创建你的React项目,输入下面的命令。

npx create-react-app contactmanager

设置React应用程序

在你刚刚创建的React应用程序中,找到package.json 文件,并将API URL(http://127.0.0.1:5000/)添加到其中,如下面的代码示例所示。

"name": "contactmanager",
  "version": "0.1.0",
  "private": true,
  "proxy":"http://127.0.0.1:5000/",
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.1"
....

接下来,创建两个文件夹,名为ComponentsPagesComponents 文件夹将携带所有的应用程序组件,而Pages 文件夹将携带页面组件。

App.js 文件

接下来,让我们导入并渲染ContactPage 组件,它承载了这个应用程序的所有其他组件。复制并粘贴代码到你的App.js 文件。

import logo from './logo.svg';
import './App.css';
import {ContactPage} from './Pages/ContactPage';

function App() {
  return (
    <div className="App">
      <ContactPage />
    </div>
  );
}

export default App;

构建组件

在本节中,我们将构建构成联系人管理器应用程序的组件。

ContactPage 组件

Pages 文件夹中创建一个名为ContactPage.js 的新文件,并将下面的代码复制和粘贴到其中。

import React, {useState, useEffect} from 'react';
import {Card} from '../Components/Card';
import {Form} from '../Components/Form';

export const ContactPage = () => {
    const [contact, setContact] = useState([])
    const [addContact, setAddContact] = useState('')

    useEffect(()=> {
        fetch('/data').then(response => {
            if (response.ok){
                return response.json()
            }
        }).then(data => setContact(data))
    }, [])

    const handleFormChange = (inputValue) => {
        setAddContact(inputValue)
    }

    const handleFormSubmit = () => {
        fetch('/data/create', {
            method: 'POST',
            body: JSON.stringify({
                name:addContact
            }),
            headers: {
                "Content-type": "application/json; charset=UTF-8"
            }
        }).then(response => response.json()).then(message => 
            {console.log(message);
            getLatestContacts();
            setAddContact('')
            })
        
    }

    const deleteContact = (name) => {
        fetch('/data/delete', {
            method: 'POST',
            body: JSON.stringify({
                name:name
            }),
            headers: {
                "Content-type": "application/json; charset=UTF-8"
            }
        }).then(response => response.json()).then(message => {
            console.log(message);
            getLatestContacts()
            })
        
    }

    const getLatestContacts = () => {
        fetch('/data').then(response => {
            if(response.ok){
                return response.json()
            }
        }).then(data => setContact(data))
    }

    
    return (
        <>
        <Form userInput={addContact} onFormChange = {handleFormChange} onFormSubmit={handleFormSubmit}/>
        <Card listOfContacts={contact} onDelete={deleteContact}/>
        </>
    )
}

在上面的代码中,我们创建了ContactPage 组件并在其中渲染了CardForm 组件。

然后,通过创建一个contact 状态和它的设置器setContact ,这将承载联系人的数据。addContact 状态和它的设置器,setAddContact ,也承载着要添加到数据库中的数据的输入。

接下来,useEffect Hook和fetch 方法从Flask应用程序中的/data 端点检索数据,将检索的数据设置为当前的contact 状态。这可以确保检索到的数据与应用程序上显示的数据是一样的。

在创建handleFormChange 函数时,它将addContact 的状态设置为输入字段的当前数据。然后,我们可以将handleFormChange 作为一个道具传递给Form 组件。

接下来,handleFormSubmit 函数向Flask中的create 端点发送数据,向数据库添加新数据,并通过将setAddContact 状态设置为空字符串来清除输入字段,同时使用getLatestContacts 函数获取创建新联系人后的最新数据。

这个函数还将contact 状态设置为使用fetch 方法检索数据后的最新数据。然后,我们可以将handleFormSubmit 作为一个道具传递给Form 组件。

最后,deleteContact 函数从Flask数据库中删除联系人,方法是使用fetch 方法向data/**delete** 端点发出请求,然后使用getLatestContacts 函数来获取删除动作后的新数据。

然后我们可以将deleteContact 函数和contact 状态传递给Card 组件。

Card 组件

Card 组件渲染了所有从Flask应用程序数据库中获取的数据。要使用Card 组件,请在组件文件夹中创建一个名为Card.js 的新文件,并将下面的代码复制并粘贴到其中。

import React from 'react';

export const Card = ({ listOfContacts, onDelete }) => {
    
    const handleDelete = (name) => {
        onDelete(name);
    }

    return(
        <>
        <h2>Contact List</h2>
        {listOfContacts.map(contact => {
            return(
                <ul key={contact.name}>
                    <li>
                        {contact.name}  
                     <button onClick={() => handleDelete(contact.name)}> x </button>
                    </li>
                </ul>
            )
        })}
        </>
    ) 
}

通过使用map函数在列表中渲染每个联系人,以映射从ContactPage 传递的listOfContact 道具中的数据,我们可以添加一个删除按钮来触发handleDelete 函数并传递要删除的特定联系人的名字。

然后,handleDelete 函数收集传来的名字,并调用从ContactPage 组件传来的onDelete 道具,执行与deleteContact 相同的功能。

这样一来,我们的联系人列表目前看起来就像下面的列表。

Contact List Showing Two Names With Xs Next To Them

Form 组件

表单组件渲染了用于向我们的应用程序提交数据的表单。要做到这一点,在Components 文件夹中创建一个名为Form.js 的新文件,并复制和粘贴以下代码。

import React from 'react';

export const Form = ({ userInput, onFormChange, onFormSubmit }) => {

    const handleChange = (event) => {
        onFormChange(event.target.value);
    }

    const handleSubmit = (event) => {
        event.preventDefault()
        onFormSubmit()
    }

    return(
        <>
        <h2>Add Contact</h2>
        <form onSubmit={handleSubmit}>
            <input type="text" required value={userInput} onChange={handleChange}></input>
            <input type="submit" ></input>
        </form>
        </>
    )
}

在这里,handleSubmit 函数被附加到表单上,而handleChange 函数被附加到名称输入元素上。

当我们在HTML文本输入框中输入数据时,handleChange 函数就会触发,而当表单被提交时,handleSubmit 函数就会触发。这是通过在handleChangehandleSubmit 中调用从ContactPage 传递的prop函数来实现的。

handleChange 中,我们调用了onFormChange. 的道具,在ContactPage 组件中执行handleFormChange 的功能,而在handleSubmit 中,我们调用了onFormChange. 的道具来执行handleFormSubmit 的功能。

这就是最后的添加联系人表单。

Add Contact Field With Submit Button

结论

最后,我们有了一个可以工作的联系人列表,我们可以有效地添加和删除联系人。

Final Contact App Showing Adding And Deleting Contacts

Python和React都是创建网络应用的绝佳选择。当利用它们时,你会注意到有几个特性,如Python的装饰器和React的钩子,都是相同的。

只有在代码语法和数据模型/传输原则方面存在一些变化,例如如何定义函数,以及React组件中的数据管理与Python类中的数据不同。

作为一个Python开发者,在构建Web应用的服务器端时,学习React很重要。感谢您的阅读,祝您编码愉快!!