青训营课程笔记
JSX和React元素
JSX是JS的扩展语法,React里基于JSX语法描述组件UI的结构。
react元素就是JS的一个对象,里面包含了一些重要属性。
//JSX标签类型:1,html标签;2,react组件作为标签(如<APP />)。
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
如何将JSX描述的UI结构和真实的DOM结构联系在一起?即怎么把React元素渲染到DOM中?
//使用render API
const element = <h1>Hello,world</h1>;
ReactDOM.render(element,document.getElementById('root'));
root节点在public-index.html中定义,渲染到该节点下:
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
React组件
React组件是react元素之上的一层封装。本质上是一个函数,签名表达:component = (props)⇒element。接受props参数,返回element元素。
-
是React应用组成和复用的基本单元。
-
React组件必须像纯函数一样运行。
纯函数:对于相同的入参返回同样的结果。无副作用。(副作用是函数内部做了函数作用域以外的事情,比如访问外部变量、全局变量,对传参做修改。常见副作用是网络请求的调用,这也是对全局变量的访问)
React组件并非真正的纯函数,只是对副作用有相应规则和约束。
-
有函数组件和类组件
-
列表渲染,列表中的每一项必须设置key属性。这是react内部出于性能优化的设计,会基于key判断当前元素是可以复用还是需要创建一个新元素。
props
父组件传递给子组件的属性,在子组件内部只读。
props根据固定的入参渲染出固定的React元素。
state
组件内部自己维护的属性,可以被修改。
如果根据变量动态渲染react元素,需要使用state,即react内部的可变状态。state只能在类组件中使用,以clock类组件为例:
import React, { Component } from 'react';
export default class Clock extends Component {
constructor(props) {
super(props);
this.state = { date: new Date() };
}
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date(),
});
}
render() {
return (
<div>
<h1>Current Time: {this.state.date.toLocaleTimeString()}.</h1>
</div>
);
}
}
当前时间赋值为初始值,初始值的更新需要setState API,即tick()中的方法。在哪里调用tick方法涉及生命周期。
两个生命周期方法:componentDidMount()指组件被挂载到DOM节点的时候被调用;componentWillUnmount()指组件被从DOM节点卸载前被调用。在组件被挂载之后,通过setInterval API创建一个循环任务,这个循环任务每隔一秒调用tick方法,实现state的更新。在组件卸载时,使用clearInterval清除setInterval 的定时任务。这个setInterval 也是一个副作用,对副作用要有相应规则和约束,所以将其放在React组件的生命周期方法中。render中不能有副作用代码。
把clock组件引入app.js,就完成了页面效果。
- 函数组件是无状态组件;类组件可能是有状态组件。
组件三种状态:挂载-更新-卸载阶段,不同阶段对应不同生命周期方法,生命周期方法只对类组件有意义。函数式组件函数式组件没有生命周期方法。但是函数组件也会有三个阶段,挂载阶段更新阶段卸载阶段。三阶段会被调用的方法:
setState
- 必须使用setState API来修改state
//Wrong
this.state.comment = 'Hello';
//Correct
this.setState({comment:'Hello'});
- 如果新状态需要依赖当前状态和props计算获得,不能给setState传入对象形式的参数,而是要传入函数形式的参数。函数接收当前应用的最新state和当前组件最新的props。这和react内部组件对于setState的触发机制有关。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
出于性能考虑,React 可能会把多个setState()调用合并成一个调用。因为this.props和this.state可能会异步更新,所以不要依赖他们的值来更新下一个状态。要解决这个问题,可以让setState()接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数。
- 对state部分修改而不是全量替换。
受控组件
如果需要和页面交互,就涉及表单元素,如input。
这里render里使用form标签,里面使用input标签。input通过value属性设置值,value值通过state中获取。当input发生变化,通过监听onchange事件,更改state中的value。onchange的响应函数,通过调用setstate API将value更新为最新的值。将state更新之后,render方法重新执行,这时候this.state.value得到的就是最新的状态值,从而实现input值的更新。
input值的来源于组件的状态,其变化也依赖于通过setstate更新组件的状态。所以input值和值的变化的管理逻辑都受控于react组件,即“通过受控组件的方式使用表单元素”。
非受控组件
表单元素的值不受react组件控制,是自己内部控制。不再有value和onchange属性。
当组件内部想使用input元素的值,就需要使用ref属性。所有JSX标签都可以设置ref属性,ref引用的是当前标签对应的元素。ref的值,必须通过React.createRef API创建一个ref类型的值,才能给ref属性设置。将ref属性复制给this.input,当想要获取input值,通过this.input获取,如this.input.current.value()。【过程?】
常用模式:组件的组合用法
解耦:不同的组件关注不同的层面。相当于在FancyBorder中打洞,具体洞里的内容在使用FancyBorder时决定。
多打几个洞:
其实就是聊天室布局。
是更灵活的组合方式。
Hooks
State Hook
管理状态的函数。返回结果是一个数组类型,第一个参数是state状态,第二个参数是一个方法,用来修改state。参数名可以随便起,习惯上是xxx,setxxx。
const[state,setState] = useState(initialState);
只能在函数组件中使用。
/** hooks 版本 */
function AddPostForm(props){
const [title, setTitle] = useState('');
const [content, setContent] = useState('');
const handleChange = (e) => {
if (e.target.name === 'title') {
setTitle(e.target.value)
} else if (e.target.name === 'content') {
setContent(e.target.value)
}
};
const handleSave = async () => {
if (title.length > 0 && content.length > 0) {
console.log(title, content)
}
};
return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="title">Post Title:</label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={handleChange}
/>
<label htmlFor="content">Content:</label>
<textarea
id="content"
name="content"
value={content}
onChange={handleChange}
/>
<button type="button" className="button" onClick={handleSave}>
Save Post
</button>
</form>
</section>
);
};
AddPostForm有两个可变状态,用两个usestate得到这两个state的值。input表单元素里的value属性和texttarea里的value属性,接收title和content状态。当状态发生改变时,onchange的响应函数,通过调用settitle和settexttarea API将状态修改。状态修改后,AddPostForm组件重新运行,input和texttarea得到更新后的title和content。AddPostForm重新运行时,useState初始值不起作用。
const [{title,content},setState] = useState({
title:'',
content:''
})
//可以,但不再是对状态的局部修改了。
Effect Hook
Effect副作用。管理react函数中的副作用。曾经组件的副作用可以放到生命周期方法中,Effect Hook也可以做到。
useEffect(()=>{effectFn();return clearFn},[...dependencies]);
useEffect接收两个参数,第一个参数是一个函数,函数内可以执行带有副作用逻辑的函数effectFn(),函数可以有一个返回值clearFn。每次执行useEffect的时候都会先执行上一次useEffect内的返回函数clearFn,所以在clearFn里可以做清除副作用的逻辑;第二个参数是可选的,数组类型参数,如果不传则每次useEffect执行,effectFn都会执行。如果传入了参数,只有数组内的变量发生变化的时候,effectFn才会被执行。如果传入了空数组,effectFn只会被执行一次,是在组件被挂载完成之后执行一次。注意:useEffect每次都会执行,但不代表effectFn会被执行。
//发送网络请求获取文章数据,发送网络请求是副作用。
function App() {
const [articles, setArticles] = useState([])
useEffect(()=> {
const fetchArticles = async () => {
try{
const response = await client.get('/fakeApi/posts');
console.log(response)
setArticles(response.posts);
}catch(err){
console.error(err)
}
}
fetchArticles();
}, [])
return (
<div>
<ArticleList articles={articles}/>
</div>
)
}
自定义Hooks
- 以use作为函数名开头,封装通用逻辑的函数
上例即模拟组件挂载阶段的生命周期函数。在组件挂载之后实现fn函数。
```
import React, { useEffect,useState } from 'react';
function useCurrentDate(){
//首先在useCurrentDate中用useState提前创建出date状态和设置date的方法
const [date, setCurrentDate] = useState(new Date());
const tick = () => {
setCurrentDate(new Date())
}
//第二在useEffect中创建一个副作用逻辑,通过setInterval创建一个定时任务
//每隔一秒调用tick函数,date状态每隔一秒更新一次
//useEffect的第一个参数函数返回一个clearInterval
//保证组件卸载时清除setInterval创建的逻辑,防止内存泄漏
useEffect(() => {
const timer = setInterval(()=>{tick()}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return date;
}
//clock组件借助hooks使用函数组件就变成了以下几行
//前提是封装出来一个useCurrentDate()的Hook函数,获取当前时间
//如果其他组件需要获取当前时间,只需调用useCurrentDate()函数
export default function Clock() {
const date = useCurrentDate();
return (
<div>
<h1>Current Time: {date.toLocaleTimeString()}.</h1>
</div>
);
}
```
类组件中,对于date相关状态管理分散在组件的各个生命周期函数中,很难复用这堆逻辑。通过自定义Hook方法,可以将状态管理逻辑封装到一个Hook函数中,在函数组件中使用,从而实现状态管理逻辑复用的目的。
Hooks规则
-
只能在函数组件、自定义hook中调用hook
-
只能在函数的最顶层控制流中调用hook
两个错误例子:
在if分支内使用hook,错误。和if这一层逻辑平级的控制流才是最顶层控制流。
在点击button的响应函数中使用hook,错误。
Why Hooks?
- 组件状态管理逻辑的封装和复用
- 面向react未来:类组件受生命周期方法、this问题限制,复杂度比函数组件高,性能优化和更高级的特性受到阻碍。通过函数组件和hooks结合,使react更轻量。
官方文档学习札记
新数据替换旧数据
var newPlayer = Object.assign({}, player, {score: 2});
// player 的值没有改变, 但是 newPlayer 的值是 {score: 2, name: 'Jeff'}
// 使用对象展开语法,就可以写成:
// var newPlayer = {...player, score: 2};
不直接修改(或改变底层数据)可以让我们追溯并复用历史记录。
跟踪数据的改变需要可变对象可以与改变之前的版本进行对比,这样整个对象树都需要被遍历一次,现在只要发现对象变成了一个新对象,那么我们就可以说对象发生改变了。
帮助我们在 React 中创建 pure components。确定不可变数据是否发生了改变,从而确定何时对组件进行重新渲染。
JSX
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合。并没有采用将标记与逻辑进行分离到不同文件,而是将二者共同存放在称之为“组件”的松散耦合单元之中。
在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。
因为 JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。
向事件处理程序传递参数
在循环中,通常我们会为事件处理函数传递额外的参数。例如,若 id 是你要删除那一行的 ID,以下两种方式都可以向事件处理函数传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。【第二种是为什么?如何隐式传递?】
事件处理
通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this。【为什么?】
如果觉得使用 bind 很麻烦,这里有两种方式可以解决。如果你正在使用实验性的 public class fields 语法,你可以使用 class fields 正确的绑定回调函数:【什么是Pubic class fields语法?】
如果你没有使用 class fields 语法,你可以在回调中使用箭头函数:此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。【为什么?】
条件渲染
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;
const unreadMessages = props.unreadMessages;
【为什么?】
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div>
);
【如果false && ... 返回什么?】
如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。