尽管创建用户界面的最流行的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的一些例子是useState 和useEffect 。
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_sqlalchemy 和flask_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"
....
接下来,创建两个文件夹,名为Components 和Pages 。Components 文件夹将携带所有的应用程序组件,而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 组件并在其中渲染了Card 和Form 组件。
然后,通过创建一个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 相同的功能。
这样一来,我们的联系人列表目前看起来就像下面的列表。
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 函数就会触发。这是通过在handleChange 和handleSubmit 中调用从ContactPage 传递的prop函数来实现的。
在handleChange 中,我们调用了onFormChange. 的道具,在ContactPage 组件中执行handleFormChange 的功能,而在handleSubmit 中,我们调用了onFormChange. 的道具来执行handleFormSubmit 的功能。
这就是最后的添加联系人表单。
结论
最后,我们有了一个可以工作的联系人列表,我们可以有效地添加和删除联系人。
Python和React都是创建网络应用的绝佳选择。当利用它们时,你会注意到有几个特性,如Python的装饰器和React的钩子,都是相同的。
只有在代码语法和数据模型/传输原则方面存在一些变化,例如如何定义函数,以及React组件中的数据管理与Python类中的数据不同。
作为一个Python开发者,在构建Web应用的服务器端时,学习React很重要。感谢您的阅读,祝您编码愉快!!