惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

www.infosecurity-magazine.com
www.infosecurity-magazine.com
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
Cloudbric
Cloudbric
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
N
News and Events Feed by Topic
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Securelist
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
D
DataBreaches.Net
S
Schneier on Security
L
LangChain Blog
Jina AI
Jina AI
M
MIT News - Artificial intelligence
Recent Announcements
Recent Announcements
T
Tenable Blog
B
Blog RSS Feed
V
Visual Studio Blog
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
T
The Exploit Database - CXSecurity.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
WordPress大学
WordPress大学
W
WeLiveSecurity
I
InfoQ
The Hacker News
The Hacker News
雷峰网
雷峰网
月光博客
月光博客
P
Privacy & Cybersecurity Law Blog
O
OpenAI News
Hacker News: Ask HN
Hacker News: Ask HN
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
The Last Watchdog
The Last Watchdog
P
Privacy International News Feed
Cyberwarzone
Cyberwarzone
S
SegmentFault 最新的问题
L
Lohrmann on Cybersecurity
人人都是产品经理
人人都是产品经理
V
V2EX
V
Vulnerabilities – Threatpost
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Cybersecurity and Infrastructure Security Agency CISA
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
阮一峰的网络日志
阮一峰的网络日志
SecWiki News
SecWiki News
Microsoft Azure Blog
Microsoft Azure Blog

0x01 byte

我在 2025 年看完的书 西班牙之行 2025 年初展望 2024 年底曼谷之行 荐书:The Blind Watchmaker 王垠传播的「自然视力恢复法」真的有用吗? 从高考志愿到职业选择 浅谈 Apple Intelligence 2024 年,我为什么开始为搜索付费 运气与努力 刷新了一下对内容审查粒度的认知 离开心动和 TapTap 如何反转一个链表? 如何高效地协作开发:一些 Google 的实践 关于 LeanCloud 被心动/TapTap 收购 small talk #3:从 IPFS 聊到 Web 的开放性 small talk #2:聊聊用 M1 芯片的新 Mac 怀念两位老师:Stan Eisenstat 和 Paul Hudak small talk #1: 聊聊你的私有云 如何在 Emacs 里做所有事 Remark Ninja: 一个简单的评论系统 Woman、man、camera、TV:如何做一个完整的深度学习应用 荐书:走出戈壁:我的中美故事 LeanCloud 开始周期性远程工作了 树莓派:用 Pi-hole 来保护隐私和过滤广告 爱国指南 我在 2019 年觉得不错的几个习惯 WeWork 的兴衰和创投的游戏 计算机专业学生该如何提高自己 怎样利用好路上的时间 荐书:Educated - 一部震撼人心的回忆录 荐书:Bad Blood,创业公司的谎言与欺诈 LeanCloud 的故事 — AVOS 时期 一点人生经验 👓 — 如何学英语 一点人生经验 👓 — 学好英语的重要性 Docker 和 Kubernetes 从听过到略懂:给程序员的旋风教程 加密货币与区块链(三):什么是信任 离开微信公众平台 加密货币和区块链(二):分布式共识与去中心化 加密货币和区块链(一):历史的重演 第一个程序员 Ada 的故事 写在 9/11 十五周年 快速开发聊天机器人 说说离职员工的期权 你所不知道的 Dijkstra 你所不知道的冯·诺伊曼 在 LeanCloud 看 Parse 的关闭 对透明薪酬的回顾 比 XCodeGhost 更邪恶的手段 30 年前就出现了 写给创业的技术人 如何有效地做 Code Review 为什么每个团队都需要 Code Review? 怀念我的外公 面向对象与函数式 「零和博弈」- 是语言在演变还是媒体在倒退 编程语言之争 传统媒体和互联网 Clojure: 现实世界的 LISP 求职时的常见错误 Emacs Smart Split (旧文)也说王垠退学
入门 React hooks + 后端集成
blog.incoming@1byte.io (江宏) · 2019-04-12 · via 0x01 byte

2019 年 2 月发布的 React 16.8 正式引入了 hook 的功能。它使得 function 组件也像 class 组件一样能维护状态,所有的组件都可以写成函数的形式,比起原有的以 class 的多个方法来维护组件生命周期的方式,简化了代码,也基本消除了因为 this 绑定的问题造成的难以发现的 bug。这篇文章就介绍一下最常用的 state hook,以及在这种新的方式下怎么与后端 API 通讯。

本文以一个管理任务的 Todo list 应用为例,可以增加新的任务,点击可以把任务标记为完成。部署好的效果可以在这里看到,代码在这个 GitHub repo。这个 demo 使用 LeanCloud 作为存储数据的后端,用的是一个 LeanCloud 开发版应用,所以可能遇到请求数超限的情况,建议在本地运行并替换进自己的 AppId 和 AppKey。

这个应用只有一个叫 App 的组件:

function App() {
  const [inputValue, setInputValue] = useState('');
  const [todos, setTodos] = useState(undefined);
  const [error, setError] = useState('');

开头先定义了它使用的状态。useState 的参数是状态的初始值,它会返回一对结果:用来读取这个状态的一个只读引用,以及一个设置状态新值的函数。这里创建了三个状态:

  • inputValue: 输入新任务的 <input> 元素的当前值
  • todos: 当前显示的任务。这里初始值设为 undefined 表示尚未加载,而 [] 则意味着已经加载过,但是为空。
  • error: 当前显示的状态信息

每次这个组件被重新渲染时,App() 这个函数都会被调用。每个 useState 只有第一次被调用时返回的状态是初始值,之后每次都会返回已经记住的当前值。这里有三个状态,React 是用调用 useState 的顺序来区分他们。可以理解为 App() 的所有状态存储在一个数组里,第一个 useState() 返回的是第一个状态,第二个 useState() 返回的是第二个状态,以此类推。所以使用 hook 必须保证这个组件函数每次运行中:

  1. useState() 的调用次数必须是一样的。
  2. 与各状态对应的 useState()的调用顺序是一样的。

这就意味着 useState() 的调用不能放在条件分支或循环中。为了避免出错,最好把所有 useState() 调用放在函数开头。

接下来是添加一个任务的函数 addTodo

  const addTodo = () => {
    saveTodo(inputValue).then(todo => {
      setInputValue('');
      setTodos(prev => [todo].concat(prev));
    }).catch(setError);
  };

这里 saveTodo() 是一个 helper 函数,会在文末介绍。在后端保存了新任务后,会把输入清空,并把新的任务加到用于显示的任务列表的前面。这里使用了设置新状态的两种方式:setInputValue('') 直接设置新值,setTodos(prev => [todo].concat(prev)) 是传递一个更新状态的函数。后者通常在新状态依赖于旧状态的时候使用。

再下一步检查任务列表有没有初始化过,如果没有的话,就查询后端数据把它初始化:

  if (todos === undefined) {
    loadTodos().then(setTodos).catch(setError);
  }

然后是定义如何切换任务的完成状态:

  const toggle = item => {
    item.set('finished', !item.get('finished'));
    item.save()
      .then(() => setTodos(prev => prev.slice(0)))
      .catch(setError);
  };

这里值得注意的是在设置 todos 的新值的时候用 prev.slice(0) 把这个数组复制了一份。这是因为切换一个任务的状态只是这个数组中一个元素的一个属性发生了改变。在使用 hook 更新状态时,作为一个优化,React 会用 Object.is() 比较新老状态,如果在这个语义下它们相等,React 会认为状态没有改变而不重新渲染这个组件。Object.is() 认为满足以下条件之一的两个值相等:

  • 两个都是 undefined
  • 两个都是 null
  • 两个都是 true 或者都是 false
  • 两个都是字符串并且有相同的长度,相同的字符以相同的顺序出现
  • 两个是同一个对象
  • 两个都是数字并且:
    • 都是 +0
    • 都是 -0
    • 都是 NaN
    • 都不是零或 NaN 并有相同的值

这对于数字、布尔、字符串这样 immutable 的简单类型来说不是问题,但是对于数组和对象来说,就意味着只有传递一个新的对象才会触发渲染。好在这里 slice(0) 只是做一个浅拷贝,没有复制数组引用的对象,所以代价是比较低的。

最后是把上面👆的一切放到渲染结果里:

  return (
    <div className={AppStyles.app}>
      <div className={AppStyles.error}>{error.toString()}</div>
      <div className={AppStyles.add}>
        <input placeholder="What to do next?" value={inputValue}
               onChange={e => setInputValue(e.target.value)}
               onKeyUp={e => { if (e.keyCode === 13) addTodo(); } } />
        <input type="button" value="↩" />
      </div>
      <ul>
        {todos && todos.map(item =>
                   <li key={item.getObjectId()}
                       onClick={() => toggle(item)}
                       data-finished={item.get('finished')}>
                     {item.get('content')}
                   </li> )}
      </ul>
    </div>
  );
}

下面两个函数是 App() 里用到的从 LeanCloud 更新和加载数据的 saveTodo()loadTodos()

function saveTodo(content) {
  const Todo = LC.Object.extend('Todo');
  const todo = new Todo();
  todo.set('content', content);
  todo.set('finished', false);
  return todo.save();
}

function loadTodos() {
  const query = new LC.Query('Todo');
  query.equalTo('finished', false);
  query.limit(20);
  query.descending('createdAt');
  return query.find();
}

有的人认为 React 的 hook 让 React 变得更加「函数式」了。我的看法恰恰相反。把什么都变成了 JavaScript 的 function 并不意味着程序更 functional 了。在有 hook 之前,React 的组件分为 class 组件和 function 组件,本来 function 组件可以看作是纯函数,传递进去的 props 能决定渲染结果,是 functional 的。有了 hook 之后 function 也可以有状态了,所以变成了披着 function 外衣的 object。如果不仔细了解实现机制的话,很容易产生一些微妙的 bug。不过也不可否认,使用 hook 开发简化了组件生命周期的概念,减少了代码量,在开发者熟悉了这个新模式之后,还是一个很有价值的改变。