JSX
可以是 html 标签,也可以是自定义组件
在 JSX 中嵌入表达式
{}
export default function App() {
const name = "Word";
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
我们可以在{}
中书写任意的 JS 表达式,也可以调用函数
function getName(user) {
return user.firstName + " " + user.lastName;
}
const user = {
firstName: "Wang",
lastName: "Hua"
};
export default function App() {
return (
<div className="App">
<h1>Hello {getName(user)}</h1>
</div>
);
}
JSX 也是一个表达式
可以在条件/循环语句代码快中使用 JSX,也可以将 JSX 赋值为一个变量,做为函数的返回值/参数
JSX 中指定属性
为标签或者组件的某一个属性赋值时,如果值是一个字符串,则直接经字符串赋值给属性;如果值是一个变量或者是数字,则使用{}
在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号。你应该仅使用引号(对于字符串值)或大括号(对于表达式)中的一个,对于同一属性不能同时使用这两种符号。
JSX 语法上更接近于 JS 而不是 HTML,所以对于属性名使用小驼峰来定义,而不是 html 的属性名。例如说在 JSX 中 class -> className
JSX 防注入攻击
可以在 JSX 中安全的插入用户输入的内容(使用{}
的方式插入)
React DOM 在渲染所有输入内容之前,默认会进行转义。可以确保所有的内容在渲染之前都会被转换为字符串
JSX 表示对象
Babel 会将 JSX 转换为 React.createElement 的函数调用
const element = <h1 className="greeting">Hello, world!</h1>;
等价于
const element = React.createElement(
"h1",
{ className: "greeting" },
"Hello, world!"
);
// 最后生成的对象,简化版
const element = {
type: "h1",
props: {
className: "greeting",
children: "Hello, world!"
}
};
这些对象就是 React 元素
元素渲染
React 元素是构成 React 应用的最小模块。
与 DOM 元素不同,React 元素是开销极小的对象。ReactDOM 负责为更新 DOM 来与 React 元素保持一致
React 只会更新它需要更新的部分,即使我们每次都传递一个新的元素给 ReactDOM.render
组件 & props
组件的作用: 代码复用,可以接受一些 props
组件分为函数式组件和 class 组件。在 React Hooks 出现之前,函数式组件是没有状态的,数据来源于 props
函数式组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
需要注意的是,自定义组件需要以大写字母开头。React 会将以小写字母开头的都视为原生的 DOM 元素
props 的只读性
在 React 中 props 是只读的,不要在组件中修改它的值/它上面的属性,如果你需要一个可以改变的值,那么可以将它定义为 state
state 和生命周期
有时候我们希望可以更新组件的数据,此时我们就需要借助 state。state 是每个组件自己私有的,完全受控于组件本身
定义一个 class 组件来使用 state
class Clock extends Component<object, { time: Date }> {
timer: null | number;
// highlight-start
constructor(props) {
super(props);
this.timer = null;
this.state = {
time: new Date()
};
}
// highlight-end
componentDidMount() {
this.timer = setInterval(() => {
this.setState({ time: new Date() });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
return <div>{this.state.time.toLocaleString()} </div>;
}
}
知识点
-
在构造函数中初始化 state
-
始终使用 props 参数来调用父类构造函数
-
在构造函数中使用 this 之前必须调用 super 函数
-
componentDidMount 方法会在组件渲染到 DOM 中后运行,渲染到 DOM 中 && 渲染到页面之前,它中的操作会阻塞页面的渲染
-
componentWillUnmount 会在组件卸载之前执行,可以在这里执行一些清除的操作
-
尽管 this.props 和 this.state 是 React 本身设置的,且都拥有特殊的含义,但是其实你可以向 class 中随意添加不参与数据流的额外字段。
-
render 方法的返回值定义组件如何展示
-
setState() 的调用,React 能够知道 state 已经改变了,然后会重新调用 render() 方法
useEffect(fn, []) 和 componentDidMount 的区别
- useEffect(fn, [])会在 commit 阶段执行完成之后异步的调用 fn 函数
- componentDidMount 会在 commit 的 mutation 阶段完成组件更新之后的 layout 阶段同步调用,此时组件还没有被渲染在页面上
可见他们的调用时机完全不同
而 useLayoutEffect(fn,[])它的调用时机和 componentDidMount 完全一致也会在 layout 阶段同步调用
commit 阶段
commit 阶段的作用就是将状态变化渲染到视图中也就是将 effect 渲染到视图中,将 commit 阶段分为三个子阶段
- 渲染视图前 before mutation 阶段
- 渲染视图 mutation 阶段 --- placement effect 会在该阶段执行 DOM 的插入操作,在 layout 阶段调用 componentDidMount
- 渲染视图后 layout 阶段
正确的使用 state
不要直接修改 state
this.state.xxx = 123;
tis.state 上的 xxx 属性的值被改变了,但是 React 并不会重新渲染页面。应该使用 this.setState 来修改 state
构造函数唯一可以对 this.state 赋值的地方
state 的更新可能是异步的
无论是使用 react hook 的useState
的setState
还是使用 class 组件的this.setState
的方式来更新 state,我们都无法在更新 state 之后立即拿到最新的 state
出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
this.setState((state, props) => ({
c: state.a + props.b
}));
State 的更新会被合并(仅限 this.setState)
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。
useState hook 不会对传入的对象和当前的 state 进行合并,所以我们每次更新是需要保证传入了完整的 state
数据是向下流动的
React 采用的是“自上而下”或是“单向”的数据流。任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
事件处理
React 中的事件和 DOM 事件很类似,只有几点不同:
- 事件名称采用小驼峰命名法(onclick(DOM) -> onClick(React))
- 在 JSX 中需要传入一个函数作为事件处理函数,而不是一个字符串
- 在 React 中不能通过返回 false 的方式来阻止默认事件,需要显示的调用 e.preventDefault()
例如
- 事件处理函数的绑定
<button onclick="activateLasers()">
Activate Lasers
</button>
<button onClick={activateLasers}>Activate Lasers</button>
- 阻止默认行为
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log("The link was clicked.");
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
注意
-
在这里,e 是一个合成事件。React 替我们处理了事件对象 event 对于不同浏览器的兼容性问题。
-
在 React 中,我们一般不需要使用 addEventListener 来为 DOM 添加事件处理函数
在类组件中绑定事件处理函数
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = { isToggleOn: true };
// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? "ON" : "OFF"}
</button>
);
}
}
ReactDOM.render(<Toggle />, document.getElementById("root"));
注意
在类组件中,为事件处理函数绑定 this 是必不可少的,否则事件处理函数中的 this 指向的就不是组件实例,而是 undefined
这个其实是 JS 函数的特性:
我们都知道 JavaScript 函数中的 this 不是在函数声明的时候定义的,而是在函数调用(即运行)的时候定义的
onClick 调用 handleClick 函数的时候,默认情况下,this 是指向全局的。但是,在 class 中默认使用严格模式,不会默认绑定,所以打印出来的 this 就是 undefined
class Foo {
constructor(name) {
this.name = name;
}
display() {
console.log(this.name);
}
}
var foo = new Foo("coco");
foo.display(); // coco
// 下面例子类似于在 React Component 中 handle 方法当作为回调函数传参
var display = foo.display;
display(); // TypeError: this is undefined
我们在实际 React 组件例子中,假设 handleClick 方法没有通过 bind 绑定,this 的值为 undefined, 它和上面例子类似 handleClick 也是作为回调函数传参形式。 但是我们代码不是在 strict 模式下, 为什么 this 的值不是全局对象,就像前面的 default binding,而是 undefined? 因为 class 内部默认是严格模式。
对于事件处理函数的 this 我们有三种处理方式:
-
在 constructor 中为事件处理函数绑定 this ✅
-
事件处理函数使用箭头函数定义 ✅
-
在为事件传递事件处理函数的时候绑定 this(不推荐,因为每次组件重新渲染的时候都会创建一个新函数)❌
向事件处理函数传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
-
箭头函数
-
bind 方法
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。(通过 bind 传递的参数,事件对象是最后一个)
条件渲染
React 中的条件渲染和 JavaScript 中的条件判断相同。可以使用 if 语句/三元表达式/逻辑与来实现
if 语句
function ShowInfo(props) {
if (props.showName) {
return "Lily";
} else {
return "-";
}
}
class App extends Component<AppProps, AppState> {
constructor(props) {
super(props);
this.state = {
name: "React"
};
}
render() {
return <ShowInfo showName={false} />;
}
}
与运算符 &&
通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。(对于 undefined/null/true/false 可以实现理想的为真时展示组件,为假时什么都不渲染。但是并不是所有的假值都可以达到理想效果)
例如
0 && <div>a</div>;
此时页面上就会展示 0
在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。
三目运算符
另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
当然也可以渲染组件,有一个分支为 null 时就可以不在页面上渲染任何东西
阻止条件渲染
null
在极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染。
在组件的 render 方法中返回 null 并不会影响组件的生命周期。
列表 & key
渲染多个组件
{}
+ JSX
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map(number => <li>{number}</li>);
ReactDOM.render(<ul>{listItems}</ul>, document.getElementById("root"));
但是,当我们运行这段代码,将会看到一个警告 a key should be provided for list items,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。
key
key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。
key 的标准:
-
在列表中独一无二的字符串,一般我们将 id 作为 key(只需要保证在兄弟元素中唯一,不需要全局唯一)
-
当元素没有一个确定的 id 时,万不得已的情况下,使用 index 作为 key
如果你选择不指定显式的 key 值,那么 React 将默认使用索引用作为列表项目的 key 值。
元素的 key 只有放在就近的数组上下文中才有意义。
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map(number => (
// 正确!key 应该在数组的上下文中被指定
<ListItem key={number.toString()} value={number} />
));
return <ul>{listItems}</ul>;
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById("root")
);
一个很好的经验法则:在 map 方法中的元素需要设置 key
key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值
const content = posts.map(post => (
<Post key={post.id} id={post.id} title={post.title} />
));
上面例子中,Post 组件可以读出 props.id,但是不能读出 props.key。
如果一个 map() 嵌套了太多层级,那可能就是你提取组件的一个好时机
表单
在 React 中,表单通常都是受控组件,即需要定义 value={state} + onChange(改变 value 绑定的 state 值)
受控组件
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。把两者结合起来,使 React 的 state 变成唯一数据源。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: "" };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
alert("提交的名字: " + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
{/*highlight-start*/}
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
{/*highlight-end*/}
</label>
<input type="submit" value="提交" />
</form>
);
}
}
由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。
对于受控组件来说,输入的值始终由 React 的 state 驱动。
textarea 标签 === input 标签
select 标签
在 HTML 中,<select>
创建下拉列表标签。
<select>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option selected value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
由于 selected 属性的缘故,椰子选项默认被选中。React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性。
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = { value: "coconut" };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({ value: event.target.value });
}
handleSubmit(event) {
alert("你喜欢的风味是: " + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
{/*highlight-start*/}
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
{/*highlight-end*/}
</label>
<input type="submit" value="提交" />
</form>
);
}
}
select 多选
你可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项:
<select multiple={true} value={['B', 'C']}>
处理多个输入
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。
需要这样做的前提:
-
每个表单组件都使用同一个 change 函数处理
-
state 的属性名称需要和把 name 值保持一致
受控组件输入空值
在受控组件上指定 value 属性会阻止用户更改输入(没有使用 onChange 去更改 value 绑定的值时)。如果你指定了 value,但输入仍可编辑,则可能是你意外地将 value 设置为 undefined 或 null。
我们项目中使用的表单
-
antd-form
-
自定义表单组件:只要这个表单有 value 属性(接受外界 form 的赋值)和 onChange 方法(将表单组件的输出给到外部的 form),name 它就是一个表单组件
import { Form, Radio } from 'antd';
import type { FormItemProps } from 'antd/lib/form';
import type { FC } from 'react';
import type { RadioChangeEvent } from 'antd/lib/radio/interface';
export interface FormRadioProps extends FormItemProps {
disabled?: boolean;
list: any[];
val2Label: Record<any, string>;
onChange?: (e: RadioChangeEvent) => void;
}
export const FormRadio: FC<FormRadioProps> = (props) => {
const { disabled = false, list, val2Label, onChange, ...formItemProps } = props;
return (
<Form.Item
// eslint-disable-next-line
{...formItemProps}
>
<Radio.Group disabled={disabled} options={list.map((val) => ({ label: val2Label[val], value: val }))} onChange={onChange} />
</Form.Item>
);
};
状态提升
当多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
组合 vs 继承
在 React 中,推荐使用组合而非继承的方式来实现代码的重用
包含关系
有些组件无法提前知道它的子组件的内容,此时推荐使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中
function FancyBorder(props) {
return (
<div className={"FancyBorder FancyBorder-" + props.color}>
{props.children}
</div>
);
}
少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">{props.left}</div>
<div className="SplitPane-right">{props.right}</div>
</div>
);
}
function App() {
return <SplitPane left={<Contacts />} right={<Chat />} />;
}
在 React 中我们可以将任意内容作为 props 传入
❗️ 留一个问题
在 angular 中怎么实现这种方式渲染组件
网友评论