React

react 页面是由各个 react 组件构成的.

基础

传值

各个组件之间传值,向外部传值用 this.props.... 
向内部传值用 this.state... 
在内部保存值用 this.setState

类有三个要素.
声明的参数
constructor 构造函数
调用的函数.

React 类

一般调用的是 render()  函数,将内部的 JSX  语法构建到虚拟 DOM 中.从而渲染到页面上.
在定义子类的构造函数时,都要调用 super()  方法.
因此,所有含有构造函数的 React 组件,必须以 super(props)  开头.

组件通信

父传子

子组件通过一个绑定的参数接受来自父组件的 props.父组件把值写在 state 中.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Name extends React.Component {
state = {
lastName = "Tom"
}
render(){
<div className="parent">
<Child name={this.state.lastName} />
//name={this.state.lastName}作为一个整体传递给子组件,作为props
</div>
}
}

function Child(props) {
return <div>父组件的数据: {props.name}</div>
}

子传父

通过回调函数,父组件提供回调函数,子组件调用,将要传递的数据通过参数传递.
父组件拿到数据进行更新 state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Parent extends React.Component {
state = {
parentName: ''
}
//声明回调
const getChildMsg = (data) => {
console.log("来自子组件",data)
this.setState({
parentName: data
})
}

render() {
return (
<div>
<p>父组件接受: {this.state.parentName}</p>
<Child getMsg={this.getChildMsg} />
</div>
)
}
}
//子组件
class Child extends React.Component {
state = {
msg: '子组件的信息'
}

const handleClick = () => {
//触发父组件的回调,并将参数传递
this.props.getMsg(this.state.msg)
}

render() {
return (
<div className="child">
<button onClick={this.handleCick}>Click me!</button>
</div>
)
}

父级的方法传递到子级的底层时,进行调用的时候,最好有所关联
filter 方法并没有更新当前的数组而是创建了一个新数组,它会过滤掉不符合条件的数据项,符合条件的数据项就会组成新的数组。
filter 是 JavaScript 中删除数组元素的首选方法。
现在我们需要将removeUser传递给组件,并且在每一个列表行上渲染一个按钮,点击这个按钮调用这个方法。我们通过属性的方式将removeUser方法传递给 Table 组件。
TableBody 中,在调用removeItem()时,将数据对应的索引号(index)作为参数传递给了removeItem()方法,这样过滤方法就知道该删除哪个数据项了。创建一个按钮,并在onClick中调用removeItem().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function App() {
const [users, setUsers] = useState([{ name: "aaa" }]);
const removeUser = (index) => {
setUsers(users.filter((use, i) => index !== i));
};
return (
<div className="app">
<Table items={users} removeItem={removeUsers} />
</div>
);
}
function Table(props) {
const { items, removeUsers } = props;
return (
<table>
<TableHeader />
<TableBody items={items} removeItem={removeItem} />
</table>
);
}
function TableBody(props) {
const { items, removeUsers } = props;
const rows = items.map((item, index) => (
<tr key={index}>
<td>{items.name}</td>
<td>{items.job}</td>
<td>
<button onClick={() => removeUSer(index)}></button>
</td>
</tr>
));
return <tbody>{rows}</tbody>;
}

注意

组件传值的时候不要直接把传递过来的函数放到组件上,建议另写一个函数调用的时候带上这个props传递的函数.
下面的handleSubmit是从props拿到的,但是又封装一个函数才进行调用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
const Form = (props) => {
const initalData = {
name: "",
age: null,
};
const [formData, setFormData] = useState(initalData);
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({
...formData,
[name]: value,
});
};
const submitForm = () => {
props.handleSubmit(formData);
setFormData(initalData);
};

return (
<>
<form>
<label>姓名</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
<label>年龄</label>
<input
type="text"
name="age"
value={formData.age}
onChange={handleChange}
/>
<button onSubmit={submitForm}>提交</button>
</form>
</>
);
};

兄弟通信

状态提升

当你遇到需要同时获取多个子组件数据,或者两个组件之间需要相互通讯的情况时,需要把子组件的  state  数据提升至其共同的父组件当中保存。之后父组件可以通过  props  将状态数据传递到子组件当中。这样应用当中所有组件的状态数据就能够更方便地同步共享了。

  • 将共享状态提升到最近的公共父组件,由公共父组件管理状态
  • 思想: 状态提升
  • 公共父组件职责: 提供共享状态,提供操作共享状态的方法
  • 要通讯的子组件只需要通过props接收或者操作状态的方法

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Parent extends React.Component {
//共享状态
state = {
count: 0
}
//共享方法
const onIncrement = () => {
this.setState({
count += 1
})
}
render() {
return (
<div>
<Child1 count={this.state.count} />
<Child2 onIncrement={this.onIncrement} />
</div>
)
}
}
//子组件
const Child1 = props => {
return (
<div>计数器: {props.count} </div>
)
}

const Child2 = props => {
return (
<button onClick={() =>props.onIncrement()}>+1</button>
)
}

命名规范

因为 DOM 元素   是一个内置组件,因此其 onClick 属性在 React 中有特殊的含义。而对于用户自定义的组件来说,命名就可以由用户自己来定义了。我们给 Square 的 onClick 和 Board 的 handleClick 赋予任意的名称,代码依旧有效。在 React 中,有一个命名规范,通常会将代表事件的监听 prop 命名为 on[Event],将处理事件的监听方法命名为 handle[Event] 这样的格式。

函数式组件

将 class 组件修改为函数式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// class Square extends React.Component {
// render() {
// return (
// <button
// className="square"
// onClick={()=> this.props.onClick()}>
// {this.props.value}
// </button>
// );
// }
// }
function Square(props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}

注意:
onClick={()=>this.props.onClick()} 
修改为
onClick={props.onClick} 
两侧括号消失

Props

  • 可以给组件传任意类型的值
  • props是只读的对象,只能读取属性的值,无法修改
  • 使用类组件时,如果写了构造函数,应该将props传递给super(),否则无法在构造函数中获取props

props 深入

children 属性

表示组件标签的子节点.当组件标签有子节点时,props 就会有该属性.
children 和普通的 props 属性一样,值可以是任意值.

1
2
3
4
5
6
7
8
9
10
11
12
const Test = () => <button>子组件</button>
const App = () => {
return (
<div>{props.children}</div>
)
}
ReactDOM.render(
<App>
{Test />
</App>,
document.getElementById("root")
)

props 校验

安装包prop-types,引入包,
使用组件名.propTypes={}来给组件的 props 添加校验规则.
校验规则通过PropTypes对象来指定

1
2
3
4
5
6
7
8
9
import PropTypes from "prop-types";
function App(props) {
return <h1>{props.colors}</h1>;
}
App.propTypes = {
//约定colors属性为array类型
//如果类型不对,则报错
colors: PropTypes.array,
};

约束规则

  1. 常见类型: array, bool, func, number, object, string
  2. React 元素类型: element
  3. 必填项: isRequired
  4. 特定结构的对象: shape({})
1
2
3
4
5
6
7
8
9
//常见类型
optionalFunc: PropTypes.func,
//必选
requiredFunc: PropTypes.func.isRequired,
//特定结构对象
optionalObjectwithShape: PropTypes.shape({
color: Proptypes.string,
fontSize: PropTypes.number
})

props 默认值

组件.defaultProps: 给组件传入默认值,在未传入 props 时生效.

State

不要直接修改 state,使用 this.setState()

1
2
3
4
5
//错误
this.state.comment = "hello";

//正确
this.setState({ comment: "hello" });

构造函数是唯一可以给 this.state  赋值的地方.

setState

setSate 接收一个函数,而函数的参数就是当前 state 的值.变化后记得将新的 state 返回出去.

1
this.setState((count) => count + 1);

state 的更新可能异步

this.props  和 this.state  可能会异步更新,不要依赖他们的值更新下一个状态.

1
2
3
4
//错误示例
this.setState({
counter: this.state.counter + this.props.increment,
});

正确解决方法:

setState  接收一个函数而不是一个对象.
这个函数用上一个 state  作为参数,将此次更新被应用时的 props  作为第二个参数:

1
2
3
4
this.setState({
(state, props) => ({
count: state.counter + props.increment
}))

state 的更新可能会被吞并

当调用 setState  时, React  会把提供的对象合并到当前的 state 中.

setState 是同步还是异步

分为两种时期的两种模式。
React17 之前的 legacy mode,和 react17 之后的 concurrent mode。

1
2
3
4
5
import ReactDOM from ‘react-dom’;
import App from './App';

const rootElement = document.getElementById('root');
ReactDOM.render(<App />,rootElement); // ReactDOM.render()就是开启legacy mode的方法

Legacy mode: 特点是同步执行。
分成两种情况:

  1. React 流程的 setState。如生命周期函数,React 事件响应函数。
  2. React 控制之外的函数。定时器,DOM 事件,Promise。

在 React 流程内,setState 是批量延后执行,也就看成了异步。React 将 setState 的各种更新先不更新,在生命周期的最后阶段才合成为一个对象,批量更新。

1
2
3
4
5
6
7
8
import ReactDOM from ‘react-dom’;
import { crreateRoot } from 'react-dom/client';

import App from './App';

const rootElement = document.getElementById('root')
const root = createRoot(rootElement)
root.render(<App />);

Concurrent Mode:并发模式。
即将 render 操作拆成一个个小的任务,异步执行。

Context

跨组件通信,也就是不同层级的组件访问相同的数据,比如当前的登录用户,主题,语言.

使用方法

使用React.createContext创建ProviderConsumer两个组件

1
const { Provider, Consumer } = React.createContext();

使用Provider作为父节点.

1
2
3
4
5
<Provider>
<div className="App">
<Child />
</div>
</Provider>

使用value属性表示要传递的数据.

1
<Provider value="red">

调用Consumer组件进行接收.Consumer 内部需要一个函数作为子元素.
即 data 接收的就是 value 的值.

1
<Consumer>{(data) => <span>data参数表示接收到的数据--{data}</span>}</Consumer>

关于 Provider 渲染

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。

Class.contextType

挂载在 class 上的 contextType 属性可以赋值为 context 对象,此属性可以让我们通过this.context来获取 Context 的值.可以在任何生命周期中访问.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;

额外的

如果想避免层层传递某些属性,也可以使用组件组合()

数据是向下流动的

任何的  state  总是所属于特定的组件,而且从该  state  派生的任何数据或  UI  只能影响树中“低于”它们的组件。
如果把一个以组件构成的树想象成一个  props  的数据瀑布的话,那么每一个组件的  state  就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。

事件处理

不能通过返回 false  阻止事件默认行为,必须显示的使用 preventDefault .

this 的绑定

将 this 绑定到构造函数上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {
isToggleOn: true,
};
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("app"));

不想 bind  的解决方法.

  1. 使用箭头函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Toggle extends React.Component {
...
handleClick = ()=> {
this.setState(state =>{
isToggleOn: !state.isToggleOn
})

render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
  1. 在 render 中使用箭头函数
1
2
3
4
5
6
7
8
render(){
return (
<button onClick={()=> {this.handleClick()}>
Clcik me
</button>
)
}

向事件处理程序传递参数

若 id 是要删除的哪一行

1
2
<button onClick={(e)=>this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

关于 Click 中的箭头函数问题

1
2
3
4
5
6
7
onClick={这里是一个函数或函数引用}
onClick={() => this.handleClick(i)},这里面就是一个匿名函数,
//点击事件发生时,会执行这个匿名函数,匿名函数再调用handleClick函数(传参i);
//其次才是this绑定的问题

// 如果直接写函数执行,是错误的,因为会在页面渲染时立即执行,而不是点击才执行
onClick={handleClick()} // error

所以,如果要传递参数时,才需要使用箭头函数,或者绑定 this.

子组件的箭头函数

1
2
3
4
5
6
7
8
9
10
11
12
<Panel handleClick={handleClick} handleResult={() => handleResult()} />;

const handleClick = (arg: string) => {};
const handleResult = (reverse = options.reverse) => {};
/*
上面如果handleClick={()=>handleClick()}或者
handleResult={handleResult}都是错的。
因为参数的问题,click参数是必传的,写成箭头函数就必须写参数,并且在子组件里是写了
handleClick={()=>handleClick(item)},所以外部也不能写箭头函数。
result参数可以不必传,如果不写箭头函数,那么就默认是react合成事件,
reverse实际是合成事件的值
*/

条件渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up</h1>;
}
function Greeting(props) {
const isLoginIn = props.isLoginIn;
if (isLoginIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}

ReactDOM.render(<Greeting isLoginIn={false} />, document.getElementBId("root"));

列表和 Key

一个元素的  key  最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的  id  来作为元素的  key.当元素没有确定  id  的时候,万不得已你可以使用元素索引  index  作为  key.
Vue不同,key写在返回式中.

map()

使用 map() 进行遍历时,记得 return .

1
2
3
4
5
let numbers = [1,2,3]
numbers.map(number => <li key={number.toString()}>{number}</li>)

//或者
numbers.map(number =>{return <li key={number.toString()}>{number}</li>)

表单

暂时空置

状态提升

在  React  应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state  都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个  state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步  state。

component composition 这种透传数据的模式

引用官网的一句话

Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据。请谨慎使用,因为这会使得组件的复用性变差。

如果你只是想避免层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。
我们把我们需要用到数据的那个组件直接丢到数据来源的 props 身上 ,然后消费数据,把消费完的组件,也就是要被渲染到页面的内容,通过 props 传回来。这就是 component compositon ,简单粗暴,我们在原来的地方,直接渲染这个组件即可
例如:我们在 Page 组件中需要传递个 Auth 组件 user 信息,它们之间有很多的深层嵌套
我们可以这么做 (官网例子)

1
2
3
4
5
6
7
8
9
10
11
// 这里真正需要user和avatarSize的其实只有Link组件
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<NavigationBar user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<Link href={user.permalink}>
<Avatar user={user} size={avatarSize} />
</Link>
//...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 这里就是把上面的Link组件提出来,把组件通过props传递下去
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...Page的子组件
<PageLayout userLink={...} />
// ... 渲染出 ...PageLayout的子组件
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}

这样我们只用传递 userLink 即可,

组合 VS 继承

包含关系

有些组件无法提前知晓它们子组件的具体内容。在  Sidebar(侧边栏)和  Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。
我们建议这些组件使用一个特殊的  children prop  来将他们的子组件传递到渲染结果中.

1
2
3
4
5
6
7
function FancyBorder(props) {
return (
<div className={"FancyBorder FancyBorder-" + props.color}>
{props.children}
</div>
);
}

JSX 标签中的所有内容都会作为一个 childrenprop 传递给 FancyBorder 组件。因为 FancyBorder 将 {props.children}  渲染在一个

少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children ,而是自行约定:将所需内容传入 props ,并使用相应的 prop 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Contacts() {
return <div className="Contacts" />;
}
function Chat() {
return <div className="Chat" />;
}
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 />} />;
}
ReactDOM.render(<App />, document.getElementById("root"));

特例关系

暂无

生命周期

创建时

constructor=>render()=>componentDidMount

生命周期 触发时机 作用
constructor 创建组件 1.初始化 state
2.为事件处理程序绑定 this
render 每次渲染都会触发 渲染 UI(注意不能调用 setState)
componentDIdMount 组件挂载(完成 DOM 渲染后) 1.发送网络请求
2.DOM 操作

更新时

执行时机: 1.setState2.forceUpdate()3.组件接收新的 props
执行顺序: render() => componentDidUpdate

钩子函数 触发时机 作用
render 每次组件渲染都会触发 渲染 UI,与挂载阶段是同一个 render
componentDidUpdate 组件更新(完成 DOM 渲染后) 1.发送网络请求

2.DOM 操作
注意如果要 setState 必须放在 if 条件中 |

1
2
3
4
5
6
7
//如果要在componentDidUpdate中setState,需要加if
componentDidUpdate(prevProps) {
//比较更新前后的props是否相同,决定是否重新渲染
if(prevProps.count !== this.props.count) {
this.setState({})
}
}

卸载时

钩子函数 触发时机 作用
componentWillUnmount 组件卸载 执行清理工作(如卸载定时器)
1
2
3
4
5
6
7
8
componentDidMount() {
this.timeId = setInterval(() => {
console.log("定时器执行")
},1000)
}
componentWillUnmount() {
clearInterval(this.timerId)
}

其他

shouldComponentUpdate

组件

组件复用

要复用什么? 1.state2.操作state的方法(组件状态逻辑)
方式: 1.render props模式 2.HOC高阶组件 3. 自定义 hooks

render props

思路: 将复用的 state 和操作 state 的方法封装到一个组件
问题 1: 如何拿到该组件中复用的 state?
方法: 在使用组件时,添加一个值为函数的 prop,通过函数参数来获取(需要组件内部实现)
问题 2: 如何渲染任意 UI?
方法: 使用该函数的返回值作为要渲染的内容.(需要组件内部实现)

render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
1
2
3
4
<Mouse render={(mouse) => {}} />
<Mouse render={(mouse) => (
<p>鼠标当前位置:{mouse.x},{mouse.y}</p>
) }/>
具体步骤:
  1. 创建 Mouse 组件,在组件中提供复用的状态逻辑代码
  2. 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
  3. 使用props.render()的返回值作为要渲染的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Mouse extends React.Component {
state = {
x = 0,
y = 0
}
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: clientY
})
}
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 这里是关键
render() {
return this.props.render(this.state)
}
}

class App extends React.Component {

render() {
return (
<div>
<Mouse render={(mouse) => {
return (
<p>鼠标位置: {mouse.x}, {mouse.y}</p>
)
}} />
<Mouse render={(mouse) => {
return (
<img src={img} alt="cat" style={{
postion: 'absolute',
top: mouse.y,
left: mouse.x
}}
</div>
)
}
}

使用 children 替代 render

1
2
3
4
5
6
7
<Mouse>
{ {(x,y)} => <p>鼠标的位置: {x},{y}</p> }
</Mouse>
// 组件内部,见上面
render(){
return this.props.children(this.state)
}

代码优化

  1. 使用 propTypes 进行校验
  2. 将事件绑定解除
1
2
3
4
5
6
Mouse.propTypes = {
children: PropTypes.func.isRequired
}
componentWillUnmount() {
window.removeEventListener('mousemove',this.handleMouseMove)
}

HOC 高阶组件

目的: 实现逻辑复用
方法: 采用包装(装饰)模式
思路: 实际上是一个函数,接收要包装的组件,返回增强后的组件
方法: 内部创建一个类组件,在类组件内提供复用的状态逻辑代码,通过 prop 将复用的状态传递给被包装的组件.
使用场景: 比如暗黑模式.将组件进行包裹.

使用步骤

  1. 创建一个函数,名称约定以with开头.
  2. 指定函数参数,参数以首字母大写开头
  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码
  4. 在该组件,渲染参数组件,同时将状态通过 prop 传递给参数组件
  5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import React from 'react';
//声明高阶组件
function withMouse(wrappedComponent) {
//创建类组件
class Mouse extends React.Component {
//在这里提供提供复用的代码和state
state = {
x = 0,
y = 0
}
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: clientY
})
}
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
//将内部的state放到包裹的组件上
return <wrappedComponent {...this.state} />
}
}
//返回出这个类
return Mouse

}

const Position = props => {
<p>鼠标位置: {props.x}, {props.y}</p>
}
const Cat =(props) => (
<img src={img} alt="cat" style={{
postion: 'absolute',
top: props.y,
left: props.x
}} />
)

//使用高阶组件
const MousePosition = withMouse(Position)
const MouseCat = withMouse(Cat)

class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
<MousePosition />
<MouseCat />
</div>
)
}
}

设置 display Name

原因: 默认情况,React 使用组件名作为 displayName,则会出现两个名字相同的情况
方法: 设置 displayName,便以区分.注意名字首字母大写.

1
2
3
4
5
6
7
8
9
10
11
12
function withMouse(wrappedComponent) {
class Mouse extends React.Component {
//...
}
//在高阶组件内设置displayName,注意首字母大写
Mouse.displayName = `WithMouse${getDisplayName(wrappedComponent)}`;
return Mouse;
}
//设置获取名称方法,注意首字母大写
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || "Component";
}

传递 props

将 props 和 state 一起放在返回的组件上

1
return <wrappedComponent {...state} {...props} />;

DOM 元素

渲染文章时防止 XSS 攻击,使用特殊的写法dangerouslySetInnerHTML={{__html:'文章内容'}}

1
2
3
4
5
6
<Content>
{contentData &&
contentData.map((item, index) => (
<p key={index} dangerouslySetInnerHTML={{ __html: item }}></p>
))}
</Content>

React 合成事件

  • jsx 上写的事件没有绑定在真实 DOM 上,而是通过事件代理的方式绑定在 document 上.
  • 冒泡到 document 上的也不是原生事件,而是合成事件.如果不想冒泡,需要使用event.preventDefault,不能使用event.stopPropagation.
  • 合成事件是有个专门的事件池来管理它们的创建和销毁.当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

React 合成事件和 dom 原生事件的顺序

  • react 的所有事件都挂载在 document 中
  • 当真实 dom 触发后冒泡到 document 后才会对 react 事件进行处理
  • 所以原生的事件会先执行
  • 然后执行 react 合成事件
  • 最后执行真正在 document 上挂载的事件

React 事件系统

React 的事件系统分为几个部分:

  • 事件注册;
  • 事件监听;
  • 事件合成;
  • 事件派发;

事件系统流程:
在 React 代码执行时,内部会自动执行事件的注册;
第一次渲染,创建 fiberRoot 时,会进行事件的监听,所有的事件通过 addEventListener 委托在 id=root 的 DOM 元素上进行监听;
在我们触发事件时,会进行事件合成,同类型事件复用一个合成事件类实例对象;
最后进行事件的派发,执行我们代码中的事件回调函数;