Langchain Academy translated
  • module-0
    • LangChain 学院
  • module-1
    • 智能体记忆
    • 智能体
    • 链式结构
    • 部署
    • 路由器
    • 最简单的图结构
  • module-2
    • 支持消息摘要与外部数据库记忆的聊天机器人
    • 支持消息摘要的聊天机器人
    • 多模式架构
    • 状态归约器
    • 状态模式
    • 消息过滤与修剪
  • module-3
    • 断点
    • 动态断点
    • 编辑图状态
    • 流式处理
    • 时间回溯
  • module-4
    • 映射-归约
    • 并行节点执行
    • 研究助手
    • 子图
  • module-5
    • 记忆代理
    • 具备记忆功能的聊天机器人
    • 基于集合架构的聊天机器人
    • 支持个人资料架构的聊天机器人
  • module-6
    • 助手
    • 连接 LangGraph 平台部署
    • 创建部署
    • 双重消息处理
  • Search
  • Previous
  • Next
  • 连接 LangGraph 平台部署
    • 创建部署
    • 使用 API
    • SDK
    • 远程图
    • 运行记录
    • 线程(Threads)
    • 跨线程内存

连接 LangGraph 平台部署¶

创建部署¶

我们刚刚为模块5中的 task_maistro 应用创建了一个部署。

  • 我们使用 LangGraph CLI 为包含 task_maistro 图的 LangGraph 服务器构建了 Docker 镜像
  • 我们使用提供的 docker-compose.yml 文件创建了三个独立的容器,基于定义的服务:
    • langgraph-redis:使用官方 Redis 镜像创建新容器
    • langgraph-postgres:使用官方 Postgres 镜像创建新容器
    • langgraph-api:使用我们预构建的 task_maistro Docker 镜像创建新容器
$ cd module-6/deployment
$ docker compose up

运行后,我们可以通过以下方式访问部署:

  • API:http://localhost:8123
  • 文档:http://localhost:8123/docs
  • LangGraph Studio:https://smith.langchain.com/studio/?baseUrl=http://127.0.0.1:8123

langgraph-platform-high-level.png

使用 API¶

LangGraph 服务器暴露了多个 API 端点用于与部署的智能体交互。

我们可以将这些端点归类为几个常见的智能体需求:

  • 运行:原子级的智能体执行
  • 线程:多轮交互或人工介入
  • 存储:长期记忆

我们可以直接在API 文档中测试请求。

SDK¶

LangGraph SDK(包含 Python 和 JS 版本)为开发者提供了友好的接口,用于与上文介绍的 LangGraph 服务端 API 进行交互。

In [ ]:
Copied!
%%capture --no-stderr
%pip install -U langgraph_sdk
%%capture --no-stderr %pip install -U langgraph_sdk
In [3]:
Copied!
from langgraph_sdk import get_client

# Connect via SDK
url_for_cli_deployment = "http://localhost:8123"
client = get_client(url=url_for_cli_deployment)
from langgraph_sdk import get_client # Connect via SDK url_for_cli_deployment = "http://localhost:8123" client = get_client(url=url_for_cli_deployment)

远程图¶

如果您正在使用 LangGraph 库,远程图也是一种直接连接到图的有效方式。

In [8]:
Copied!
%%capture --no-stderr
%pip install -U langchain_openai langgraph langchain_core
%%capture --no-stderr %pip install -U langchain_openai langgraph langchain_core
In [4]:
Copied!
from langgraph.pregel.remote import RemoteGraph
from langchain_core.messages import convert_to_messages
from langchain_core.messages import HumanMessage, SystemMessage

# Connect via remote graph
url = "http://localhost:8123"
graph_name = "task_maistro" 
remote_graph = RemoteGraph(graph_name, url=url)
from langgraph.pregel.remote import RemoteGraph from langchain_core.messages import convert_to_messages from langchain_core.messages import HumanMessage, SystemMessage # Connect via remote graph url = "http://localhost:8123" graph_name = "task_maistro" remote_graph = RemoteGraph(graph_name, url=url)

运行记录¶

"运行"代表您图表的单次执行过程。每次客户端发起请求时:

  1. HTTP工作线程会生成唯一的运行ID
  2. 该运行记录及其结果会被存储在PostgreSQL中
  3. 您可以查询这些运行记录来:
    • 检查运行状态
    • 获取运行结果
    • 追踪执行历史

您可以在此处查看完整的各类运行操作指南。

下面我们来看看运行记录的一些有趣用途。

后台运行¶

LangGraph服务器支持两种运行类型:

  • 即发即弃型 - 在后台启动运行,但不等待其完成
  • 等待响应型(阻塞或轮询) - 启动运行并等待/流式传输其输出

在处理长时间运行的智能体时,后台运行和轮询机制非常实用。

让我们通过示例了解其工作原理:

In [6]:
Copied!
# Create a thread
thread = await client.threads.create()
thread
# Create a thread thread = await client.threads.create() thread
Out[6]:
{'thread_id': '7f71c0dd-768b-4e53-8349-42bdd10e7caf',
 'created_at': '2024-11-14T19:36:08.459457+00:00',
 'updated_at': '2024-11-14T19:36:08.459457+00:00',
 'metadata': {},
 'status': 'idle',
 'config': {},
 'values': None}
In [7]:
Copied!
# Check any existing runs on a thread
thread = await client.threads.create()
runs = await client.runs.list(thread["thread_id"])
print(runs)
# Check any existing runs on a thread thread = await client.threads.create() runs = await client.runs.list(thread["thread_id"]) print(runs)
[]
In [8]:
Copied!
# Ensure we've created some ToDos and saved them to my user_id
user_input = "Add a ToDo to finish booking travel to Hong Kong by end of next week. Also, add a ToDo to call parents back about Thanksgiving plans."
config = {"configurable": {"user_id": "Test"}}
graph_name = "task_maistro" 
run = await client.runs.create(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config)
# Ensure we've created some ToDos and saved them to my user_id user_input = "Add a ToDo to finish booking travel to Hong Kong by end of next week. Also, add a ToDo to call parents back about Thanksgiving plans." config = {"configurable": {"user_id": "Test"}} graph_name = "task_maistro" run = await client.runs.create(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config)
In [9]:
Copied!
# Kick off a new thread and a new run
thread = await client.threads.create()
user_input = "Give me a summary of all ToDos."
config = {"configurable": {"user_id": "Test"}}
graph_name = "task_maistro" 
run = await client.runs.create(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config)
# Kick off a new thread and a new run thread = await client.threads.create() user_input = "Give me a summary of all ToDos." config = {"configurable": {"user_id": "Test"}} graph_name = "task_maistro" run = await client.runs.create(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config)
In [10]:
Copied!
# Check the run status
print(await client.runs.get(thread["thread_id"], run["run_id"]))
# Check the run status print(await client.runs.get(thread["thread_id"], run["run_id"]))
{'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21', 'created_at': '2024-11-14T19:38:29.394777+00:00', 'updated_at': '2024-11-14T19:38:29.394777+00:00', 'metadata': {}, 'status': 'pending', 'kwargs': {'input': {'messages': [{'id': None, 'name': None, 'type': 'human', 'content': 'Give me a summary of all ToDos.', 'example': False, 'additional_kwargs': {}, 'response_metadata': {}}]}, 'config': {'metadata': {'created_by': 'system'}, 'configurable': {'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'user_id': 'Test', 'graph_id': 'task_maistro', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21'}}, 'webhook': None, 'subgraphs': False, 'temporary': False, 'stream_mode': ['values'], 'feedback_keys': None, 'interrupt_after': None, 'interrupt_before': None}, 'multitask_strategy': 'reject'}

我们可以看到它显示 'status': 'pending',因为运行仍在进行中。

如果我们想等待运行完成,使其成为阻塞式运行该怎么办?

可以使用 client.runs.join 来等待运行完成。

这样可以确保在当前线程上的运行完成之前,不会启动新的运行。

In [11]:
Copied!
# Wait until the run completes
await client.runs.join(thread["thread_id"], run["run_id"])
print(await client.runs.get(thread["thread_id"], run["run_id"]))
# Wait until the run completes await client.runs.join(thread["thread_id"], run["run_id"]) print(await client.runs.get(thread["thread_id"], run["run_id"]))
{'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21', 'created_at': '2024-11-14T19:38:29.394777+00:00', 'updated_at': '2024-11-14T19:38:29.394777+00:00', 'metadata': {}, 'status': 'success', 'kwargs': {'input': {'messages': [{'id': None, 'name': None, 'type': 'human', 'content': 'Give me a summary of all ToDos.', 'example': False, 'additional_kwargs': {}, 'response_metadata': {}}]}, 'config': {'metadata': {'created_by': 'system'}, 'configurable': {'run_id': '1efa2c00-63e4-6f4a-9c5b-ca3f5f9bff07', 'user_id': 'Test', 'graph_id': 'task_maistro', 'thread_id': '641c195a-9e31-4250-a729-6b742c089df8', 'assistant_id': 'ea4ebafa-a81d-5063-a5fa-67c755d98a21'}}, 'webhook': None, 'subgraphs': False, 'temporary': False, 'stream_mode': ['values'], 'feedback_keys': None, 'interrupt_after': None, 'interrupt_before': None}, 'multitask_strategy': 'reject'}

现在运行状态显示为 'status': 'success',表示已完成。

流式运行¶

每次客户端发起流式请求时:

  1. HTTP 工作进程会生成唯一的运行ID
  2. 队列工作进程开始处理该次运行任务
  3. 执行过程中,队列工作进程会将更新发布到Redis
  4. HTTP 工作进程订阅该次运行在Redis中的更新,并将其返回给客户端

这样就实现了流式传输!

我们已在先前模块介绍过流式传输,现在让我们选取其中一种方法——流式令牌传输——进行重点说明。

当处理可能需要较长时间才能完成的生产环境代理时,将令牌流式传输回客户端特别有用。

我们使用 stream_mode="messages-tuple" 来实现消息令牌流式传输。

In [12]:
Copied!
user_input = "What ToDo should I focus on first."
async for chunk in client.runs.stream(thread["thread_id"], 
                                      graph_name, 
                                      input={"messages": [HumanMessage(content=user_input)]},
                                      config=config,
                                      stream_mode="messages-tuple"):

    if chunk.event == "messages":
        print("".join(data_item['content'] for data_item in chunk.data if 'content' in data_item), end="", flush=True)
user_input = "What ToDo should I focus on first." async for chunk in client.runs.stream(thread["thread_id"], graph_name, input={"messages": [HumanMessage(content=user_input)]}, config=config, stream_mode="messages-tuple"): if chunk.event == "messages": print("".join(data_item['content'] for data_item in chunk.data if 'content' in data_item), end="", flush=True)
You might want to focus on "Call parents back about Thanksgiving plans" first. It has a shorter estimated time to complete (15 minutes) and doesn't have a specific deadline, so it could be a quick task to check off your list. Once that's done, you can dedicate more time to "Finish booking travel to Hong Kong," which is more time-consuming and has a deadline.

线程(Threads)¶

虽然单次运行(run)仅代表图(graph)的一次执行,但线程支持多轮交互。

当客户端使用 thread_id 执行图时,服务器会将运行中的所有检查点(步骤)保存到 Postgres 数据库的对应线程中。

服务器允许我们查看已创建线程的状态。

检查线程状态¶

此外,我们可以轻松访问保存到特定线程的所有状态检查点。

In [13]:
Copied!
thread_state = await client.threads.get_state(thread['thread_id'])
for m in convert_to_messages(thread_state['values']['messages']):
    m.pretty_print()
thread_state = await client.threads.get_state(thread['thread_id']) for m in convert_to_messages(thread_state['values']['messages']): m.pretty_print()
================================ Human Message =================================

Give me a summary of all ToDos.
================================== Ai Message ==================================

Here's a summary of your current ToDo list:

1. **Task:** Finish booking travel to Hong Kong
   - **Status:** Not started
   - **Deadline:** November 22, 2024
   - **Solutions:** 
     - Check flight prices on Skyscanner
     - Book hotel through Booking.com
     - Arrange airport transfer
   - **Estimated Time to Complete:** 120 minutes

2. **Task:** Call parents back about Thanksgiving plans
   - **Status:** Not started
   - **Deadline:** None
   - **Solutions:** 
     - Check calendar for availability
     - Discuss travel arrangements
     - Confirm dinner plans
   - **Estimated Time to Complete:** 15 minutes

Let me know if there's anything else you'd like to do with your ToDo list!
================================ Human Message =================================

What ToDo should I focus on first.
================================== Ai Message ==================================

You might want to focus on "Call parents back about Thanksgiving plans" first. It has a shorter estimated time to complete (15 minutes) and doesn't have a specific deadline, so it could be a quick task to check off your list. Once that's done, you can dedicate more time to "Finish booking travel to Hong Kong," which is more time-consuming and has a deadline.

复制线程¶

我们还可以复制(即"分叉")一个已有的线程。

这将保留原线程的历史记录,同时允许我们创建不影响原始线程的独立运行分支。

In [14]:
Copied!
# Copy the thread
copied_thread = await client.threads.copy(thread['thread_id'])
# Copy the thread copied_thread = await client.threads.copy(thread['thread_id'])
In [15]:
Copied!
# Check the state of the copied thread
copied_thread_state = await client.threads.get_state(copied_thread['thread_id'])
for m in convert_to_messages(copied_thread_state['values']['messages']):
    m.pretty_print()
# Check the state of the copied thread copied_thread_state = await client.threads.get_state(copied_thread['thread_id']) for m in convert_to_messages(copied_thread_state['values']['messages']): m.pretty_print()
================================ Human Message =================================

Give me a summary of all ToDos.
================================== Ai Message ==================================

Here's a summary of your current ToDo list:

1. **Task:** Finish booking travel to Hong Kong
   - **Status:** Not started
   - **Deadline:** November 22, 2024
   - **Solutions:** 
     - Check flight prices on Skyscanner
     - Book hotel through Booking.com
     - Arrange airport transfer
   - **Estimated Time to Complete:** 120 minutes

2. **Task:** Call parents back about Thanksgiving plans
   - **Status:** Not started
   - **Deadline:** None
   - **Solutions:** 
     - Check calendar for availability
     - Discuss travel arrangements
     - Confirm dinner plans
   - **Estimated Time to Complete:** 15 minutes

Let me know if there's anything else you'd like to do with your ToDo list!
================================ Human Message =================================

What ToDo should I focus on first.
================================== Ai Message ==================================

You might want to focus on "Call parents back about Thanksgiving plans" first. It has a shorter estimated time to complete (15 minutes) and doesn't have a specific deadline, so it could be a quick task to check off your list. Once that's done, you can dedicate more time to "Finish booking travel to Hong Kong," which is more time-consuming and has a deadline.

人机协同机制¶

我们已在模块3中探讨过人机协同机制,当前服务器支持我们讨论过的所有人机协同功能。

例如,我们可以搜索、编辑并从任意历史检查点继续执行图谱。

In [16]:
Copied!
# Get the history of the thread
states = await client.threads.get_history(thread['thread_id'])

# Pick a state update to fork
to_fork = states[-2]
to_fork['values']
# Get the history of the thread states = await client.threads.get_history(thread['thread_id']) # Pick a state update to fork to_fork = states[-2] to_fork['values']
Out[16]:
{'messages': [{'content': 'Give me a summary of all ToDos.',
   'additional_kwargs': {'example': False,
    'additional_kwargs': {},
    'response_metadata': {}},
   'response_metadata': {},
   'type': 'human',
   'name': None,
   'id': '3680da45-e3a5-4a47-b5b1-4fd4d3e8baf9',
   'example': False}]}
In [17]:
Copied!
to_fork['values']['messages'][0]['id']
to_fork['values']['messages'][0]['id']
Out[17]:
'3680da45-e3a5-4a47-b5b1-4fd4d3e8baf9'
In [18]:
Copied!
to_fork['next']
to_fork['next']
Out[18]:
['task_mAIstro']
In [19]:
Copied!
to_fork['checkpoint_id']
to_fork['checkpoint_id']
Out[19]:
'1efa2c00-6609-67ff-8000-491b1dcf8129'

让我们来编辑状态。还记得 messages 的 reducer 是如何工作的吗:

  • 默认会追加消息,除非我们提供了消息 ID
  • 当我们提供消息 ID 时,会覆盖原有消息而非追加到状态中!
In [20]:
Copied!
forked_input = {"messages": HumanMessage(content="Give me a summary of all ToDos that need to be done in the next week.",
                                         id=to_fork['values']['messages'][0]['id'])}

# Update the state, creating a new checkpoint in the thread
forked_config = await client.threads.update_state(
    thread["thread_id"],
    forked_input,
    checkpoint_id=to_fork['checkpoint_id']
)
forked_input = {"messages": HumanMessage(content="Give me a summary of all ToDos that need to be done in the next week.", id=to_fork['values']['messages'][0]['id'])} # Update the state, creating a new checkpoint in the thread forked_config = await client.threads.update_state( thread["thread_id"], forked_input, checkpoint_id=to_fork['checkpoint_id'] )
In [21]:
Copied!
# Run the graph from the new checkpoint in the thread
async for chunk in client.runs.stream(thread["thread_id"], 
                                      graph_name, 
                                      input=None,
                                      config=config,
                                      checkpoint_id=forked_config['checkpoint_id'],
                                      stream_mode="messages-tuple"):

    if chunk.event == "messages":
        print("".join(data_item['content'] for data_item in chunk.data if 'content' in data_item), end="", flush=True)
# Run the graph from the new checkpoint in the thread async for chunk in client.runs.stream(thread["thread_id"], graph_name, input=None, config=config, checkpoint_id=forked_config['checkpoint_id'], stream_mode="messages-tuple"): if chunk.event == "messages": print("".join(data_item['content'] for data_item in chunk.data if 'content' in data_item), end="", flush=True)
Here's a summary of your ToDos that need to be done in the next week:

1. **Finish booking travel to Hong Kong**
   - **Status:** Not started
   - **Deadline:** November 22, 2024
   - **Solutions:** 
     - Check flight prices on Skyscanner
     - Book hotel through Booking.com
     - Arrange airport transfer
   - **Estimated Time to Complete:** 120 minutes

It looks like this task is due soon, so you might want to prioritize it. Let me know if there's anything else you need help with!

跨线程内存¶

在模块5中,我们介绍了如何使用 LangGraph 内存存储 来保存跨线程的信息。

我们部署的图 task_maistro 使用 store 来保存信息(例如待办事项),这些信息通过 user_id 进行命名空间隔离。

我们的部署包含一个 PostgreSQL 数据库,用于存储这些长期(跨线程)记忆。

通过 LangGraph SDK,我们提供了多种与存储交互的方法。

搜索条目¶

task_maistro 图默认使用 store 保存按 (todo, todo_category, user_id) 命名空间隔离的待办事项。

默认情况下,todo_category 设置为 general(如你在 deployment/configuration.py 中所见)。

我们只需提供这个元组即可搜索所有待办事项。

In [29]:
Copied!
items = await client.store.search_items(
    ("todo", "general", "Test"),
    limit=5,
    offset=0
)
items['items']
items = await client.store.search_items( ("todo", "general", "Test"), limit=5, offset=0 ) items['items']
Out[29]:
[{'value': {'task': 'Finish booking travel to Hong Kong',
   'status': 'not started',
   'deadline': '2024-11-22T23:59:59',
   'solutions': ['Check flight prices on Skyscanner',
    'Book hotel through Booking.com',
    'Arrange airport transfer'],
   'time_to_complete': 120},
  'key': '18524803-c182-49de-9b10-08ccb0a06843',
  'namespace': ['todo', 'general', 'Test'],
  'created_at': '2024-11-14T19:37:41.664827+00:00',
  'updated_at': '2024-11-14T19:37:41.664827+00:00'},
 {'value': {'task': 'Call parents back about Thanksgiving plans',
   'status': 'not started',
   'deadline': None,
   'solutions': ['Check calendar for availability',
    'Discuss travel arrangements',
    'Confirm dinner plans'],
   'time_to_complete': 15},
  'key': '375d9596-edf8-4de2-985b-bacdc623d6ef',
  'namespace': ['todo', 'general', 'Test'],
  'created_at': '2024-11-14T19:37:41.664827+00:00',
  'updated_at': '2024-11-14T19:37:41.664827+00:00'}]

添加条目¶

在我们的图中,我们通过调用 put 方法向存储中添加条目。

若需在图外直接向存储中添加条目,我们可以使用 SDK 中的 put 方法。

In [30]:
Copied!
from uuid import uuid4
await client.store.put_item(
    ("testing", "Test"),
    key=str(uuid4()),
    value={"todo": "Test SDK put_item"},
)
from uuid import uuid4 await client.store.put_item( ("testing", "Test"), key=str(uuid4()), value={"todo": "Test SDK put_item"}, )
In [31]:
Copied!
items = await client.store.search_items(
    ("testing", "Test"),
    limit=5,
    offset=0
)
items['items']
items = await client.store.search_items( ("testing", "Test"), limit=5, offset=0 ) items['items']
Out[31]:
[{'value': {'todo': 'Test SDK put_item'},
  'key': '3de441ba-8c79-4beb-8f52-00e4dcba68d4',
  'namespace': ['testing', 'Test'],
  'created_at': '2024-11-14T19:56:30.452808+00:00',
  'updated_at': '2024-11-14T19:56:30.452808+00:00'},
 {'value': {'todo': 'Test SDK put_item'},
  'key': '09b9a869-4406-47c5-a635-4716bd79a8b3',
  'namespace': ['testing', 'Test'],
  'created_at': '2024-11-14T19:53:24.812558+00:00',
  'updated_at': '2024-11-14T19:53:24.812558+00:00'}]

删除条目¶

我们可以使用 SDK 通过键值从存储中删除条目。

In [32]:
Copied!
[item['key'] for item in items['items']]
[item['key'] for item in items['items']]
Out[32]:
['3de441ba-8c79-4beb-8f52-00e4dcba68d4',
 '09b9a869-4406-47c5-a635-4716bd79a8b3']
In [33]:
Copied!
await client.store.delete_item(
       ("testing", "Test"),
        key='3de441ba-8c79-4beb-8f52-00e4dcba68d4',
    )
await client.store.delete_item( ("testing", "Test"), key='3de441ba-8c79-4beb-8f52-00e4dcba68d4', )
In [34]:
Copied!
items = await client.store.search_items(
    ("testing", "Test"),
    limit=5,
    offset=0
)
items['items']
items = await client.store.search_items( ("testing", "Test"), limit=5, offset=0 ) items['items']
Out[34]:
[{'value': {'todo': 'Test SDK put_item'},
  'key': '09b9a869-4406-47c5-a635-4716bd79a8b3',
  'namespace': ['testing', 'Test'],
  'created_at': '2024-11-14T19:53:24.812558+00:00',
  'updated_at': '2024-11-14T19:53:24.812558+00:00'}]
# Getting Started with React

## Installation

To install React, run the following command in your terminal:

```bash
npm install react react-dom

Creating a New Project¶

The easiest way to create a new React project is by using Create React App:

npx create-react-app my-app
cd my-app
npm start

Project Structure¶

A newly created React project has the following structure:

my-app/
├── node_modules/
├── public/
│   ├── favicon.ico
│   ├── index.html
│   └── ...
├── src/
│   ├── App.css
│   ├── App.js
│   ├── index.js
│   └── ...
├── package.json
└── README.md

Basic Concepts¶

React applications are built using components. Here's a simple example:

function Welcome() {
  return <h1>Hello, World!</h1>;
}

Key Features¶

  • Declarative: Makes your code more predictable
  • Component-Based: Build encapsulated components
  • Learn Once, Write Anywhere: Works with other libraries

```markdown
# React 入门指南

## 安装

要安装 React,请在终端中运行以下命令:

```bash
npm install react react-dom

创建新项目¶

使用 Create React App 是创建新 React 项目最简单的方式:

npx create-react-app my-app
cd my-app
npm start

项目结构¶

新创建的 React 项目具有以下目录结构:

my-app/
├── node_modules/
├── public/
│   ├── favicon.ico
│   ├── index.html
│   └── ...
├── src/
│   ├── App.css
│   ├── App.js
│   ├── index.js
│   └── ...
├── package.json
└── README.md

基本概念¶

React 应用通过组件构建。以下是一个简单示例:

function Welcome() {
  return <h1>Hello, World!</h1>;
}

核心特性¶

  • 声明式:使代码更具可预测性
  • 组件化:构建封装的组件
  • 一次学习,随处编写:可与其他库协同工作

Documentation built with MkDocs.

Search

From here you can search these documents. Enter your search terms below.

Keyboard Shortcuts

Keys Action
? Open this help
n Next page
p Previous page
s Search