创建脚手架命令
推荐使用:npx create-react-app my-app
npm init react-app my-app
yarn create react-app my-app
脚手架使用React
1.导入react和react-dom
2.调用React.createElement()创建react元素
3.调用React.render()方法渲染 react 元素到页面中
// 导入react
import React from 'react';
import ReactDOM from 'react-dom';
// 创建
const title = React.createElement('h1', null, 'hello react')
// 渲染
ReactDOM.render(title, document.querySelector('#root'))
JSX基本学习
JSX的基本使用
React.createElement() 解决他的问题 可读差 看不出结构 JSX 就是JavaScript XML 优势:更加直观
提高了开发效率,降低了学习成本
import React from 'react';
import ReactDOM from 'react-dom';
// 使用JXS创建react元素
const title = <h1>hello react!</h1>
// 渲染
ReactDOM.render(title, document.querySelector('#root'))
注意点
1.特殊属性名:class -> className
2.属性名使用驼峰命名法
3.没有子节点的React元素可以用 /> 结束
4.推荐使用小括号包裹
使用JavaScript表达式注意点:
1.单大括号可以用JavaScript的任意表达式
2.JSX自身也是JS表达式
3.JS中的对象是一个例外,一般会出现在style属性中
4.不能使用语句
条件渲染
import React from 'react';
import ReactDOM from 'react-dom';
// 使用JXS创建react元素
const isLoading = true
const loadData = () => {
if (isLoading) {
return <div>loading...</div> // 还可写三元表达式 和 逻辑与运算符
}
return <div>数据加载完成此处显示加载后的数据</div>
}
const title = (
<h1>
条件渲染:
{loadData()}
</h1>
)
// 渲染
ReactDOM.render(title, document.querySelector('#root'))
列表渲染
import React from 'react';
import ReactDOM from 'react-dom';
// 使用JXS创建react元素
const songs = [
{ id: 1, name: '痴心绝对' },
{ id: 2, name: '江南' },
{ id: 3, name: '白羊' },
]
const list = (
<ul>
{songs.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
)
// 渲染
ReactDOM.render(list, document.querySelector('#root'))
注意:
1.使用map()
2.绑定key 哪个渲染绑定给谁 key最好使用唯一性
样式处理
行内样式style
const list = (
<ul>
{songs.map(item => <li style={{ color: 'red', background: 'skyblue' }} key={item.id}>{item.name}</li>)}
</ul>
)
类名—className(推荐)
import './css/index'
const list = (
<ul>
{songs.map(item => <li className="title" style={{ color: 'red', background: 'skyblue' }} key={item.id}>{item.name}</li>)}
</ul>
)
组件学习
函数组件
import React from 'react';
import ReactDOM from 'react-dom';
// 创建函数组件
function Hello() {
return (
<div>这是我的第一个函数组件</div>
)
}
// 箭头函数
const Hello = () => <div>这是我的第一个箭头函数组件</div>
// 渲染
ReactDOM.render(<Hello />, document.querySelector('#root'))
注意:
1.名字是大写开头
2.必须写返回值
类组件
import React from 'react';
import ReactDOM from 'react-dom';
// 使用类创建组件
class Hello extends React.Component {
render() {
return <div>Hello Class Components!</div>
}
}
// 渲染
ReactDOM.render(<Hello />, document.querySelector('#root'))
注意:
1.名字开头大写
2.必须继承React.Component
3.必须调用render()
4.必须要有返回值
抽离组件为独立的JS文件
1.创建一个Hello.js
import React from "react";
// 创建组件
class Hello extends React.Component {
render() {
return <div>第一个抽离的组件</div>
}
}
export {
Hello
}
2.在index中导入
import React from 'react';
import ReactDOM from 'react-dom';
// 导入Hello
import Hello from './Hello'
// 渲染
ReactDOM.render(<Hello />, document.querySelector('#root'))
React事件处理
1.事件绑定
- 语法:on+事件名称={事件处理程序};比如:
onClick={()=>{}} - 注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
import React from 'react';
import ReactDOM from 'react-dom';
/**
* React 事件处理
*/
class App extends React.Component {
handleClick() {
console.log('触发了点击事件');
}
render() {
return <button onClick={this.handleClick}>点我,点我</button>
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'))
- 函数组件绑定事件
import React from 'react';
import ReactDOM from 'react-dom';
function App() {
function handleClick() {
console.log('触发了点击按钮');
}
return (
<button onClick={handleClick}>点我</button>
)
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'))
2.事件对象
- 可以通过事件处理程序的参数获取到事件对象
- React 中的事件对象叫做:合成事件(对象)
- 合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
import React from 'react';
import ReactDOM from 'react-dom';
function App() {
function handleClick(e) {
// 阻止浏览器默认行为
e.preventDefault()
console.log('触发了点击按钮');
}
return (
<a href="http://www.baidu.com" onClick={handleClick}>百度</a>
)
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'))
state的基本使用
import React from 'react';
import ReactDOM from 'react-dom';
/**
* state的基本使用
*/
class App extends React.Component {
// constructor() {
// super();
// this.state = {
// count: 0,
// };
// }
state = {
count: 0,
};
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button
onClick={() => {
this.setState({
count: this.state.count + 1,
});
}}
>
+1
</button>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
注意:
1.处理this的值为undefined
- 箭头函数
import React from 'react';
import ReactDOM from 'react-dom';
/**
* state的基本使用
*/
class App extends React.Component {
// constructor() {
// super();
// this.state = {
// count: 0,
// };
// }
state = {
count: 0,
};
onIncrement() {
this.setState({
count: this.state.count + 1,
});
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
{/* <button
onClick={() => {
this.setState({
count: this.state.count + 1,
});
}}
>
+1
</button> */}
<button onClick={() => this.onIncrement()}>+1</button>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
bind()
import React from 'react';
import ReactDOM from 'react-dom';
/**
* state的基本使用
*/
class App extends React.Component {
constructor() {
super();
this.onIncrement = this.onIncrement.bind(this);
}
state = {
count: 0,
};
onIncrement() {
this.setState({
count: this.state.count + 1,
});
}
render() {
return (
<div>
<h1>计数器:{this.state.count}</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
表单处理
1.受控组件
import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';
/**
* state的基本使用
*/
class App extends React.Component {
// constructor() {
// super();
// this.onIncrement = this.onIncrement.bind(this);
// }
state = {
txt: '',
};
handleChange = (e) => {
this.setState({
txt: e.target.value,
});
};
render() {
return (
<div>
<input
type="text"
value={this.state.txt}
onChange={this.handleChange}
/>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
多表单元素优化步骤
- 给表单添加name属性
- 根据表单元素类型获取对应值
- 在 change 事件处理程序中 通过 [name] 来修改对应的state
import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';
/**
* state的基本使用
*/
class App extends React.Component {
state = {
txt: '',
city: 'sh',
content: '',
isChecked: false,
};
handleForm = (e) => {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value,
});
};
render() {
return (
<div>
{/* 文本框 */}
<input
type="text"
value={this.state.txt}
name="txt"
onChange={this.handleForm}
/>
<br />
{/* 富文本框 */}
<textarea
name="content"
value={this.state.content}
onChange={this.handleForm}
></textarea>
<br />
{/* 下拉框 */}
<select value={this.state.city} name="city" onChange={this.handleForm}>
<option value="sh">上海</option>
<option value="bj">北京</option>
</select>
<br />
{/* 多表单元素优化步骤 */}
{/* <input
type="text"
value={this.state.txt}
name="txt"
onChange={this.handleForm}
/> */}
<br />
{/* 复选框 */}
<input
type="checkbox"
name="isChecked"
value={this.state.isChecked}
onChange={this.handleForm}
/>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
2.非受控组件
- 调用react.creareRef() 方法创建一个ref对象
- 将创建好的 ref 对象添加 到文本框中
- 通过ref 对象获取到文本框的值
import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';
/**
* 非受控组件
*/
class App extends React.Component {
constructor() {
super();
// 创建ref
this.txtRef = React.createRef();
}
// 获取文本框的值
getTxt = () => {
console.log('文本框的值:', this.txtRef.current.value); // 获取当前的值
};
render() {
return (
<div>
{/* 绑定ref */}
<input type="text" ref={this.txtRef} />
<button onClick={this.getTxt}>获取文本框的值</button>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
小案例练习
import * as React from 'react';
import ReactDOM from 'react-dom';
// import Button from '@material-ui/core/Button';
/**
* 非受控组件
*/
class App extends React.Component {
// 初始化状态
state = {
name: '',
content: '',
comments: [
{ id: 1, name: 'jack', content: '沙发!!!' },
{ id: 2, name: 'rose', content: '板凳' },
{ id: 3, name: 'tom', content: '123' },
],
};
// 处理表单元素的值
handleForm = (e) => {
const target = e.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value,
});
};
// 发表评论
comment = () => {
const { name, content, comments } = this.state;
if (name.trim().length === 0 || content.trim().length === 0) {
alert('请输入完成信息哦~');
this.setState({
name: '',
content: '',
});
return;
}
const newComments = [
...comments,
{
id: comments.length > 0 ? comments[comments.length - 1].id + 1 : 1,
name,
content,
},
];
// 返回一个新的数据设置上去
this.setState({
comments: newComments,
name: '',
content: '',
});
};
//渲染评论列表
renderList = () => {
if (this.state.comments.length === 0) {
return <div className="no-comment">暂无评论,快去评论吧</div>;
}
return (
<ul>
{this.state.comments.map((item) => (
<li key={item.id}>
<h3>评论人:{item.name}</h3>
<p>评论内容:{item.content}</p>
</li>
))}
</ul>
);
};
render() {
return (
<div className="app">
<div>
<input
name="name"
value={this.state.name}
onChange={this.handleForm}
type="text"
placeholder="请输入评论人"
id=""
/>
<br />
<textarea
name="content"
value={this.state.content}
onChange={this.handleForm}
cols="30"
rows="10"
placeholder="请输入评论内容"
></textarea>
<br />
<button onClick={this.comment}>发表评论</button>
</div>
{/* 通过条件渲染什么内容 */}
{this.renderList()}
{/* {this.state.comments.length === 0 ? (
<div className="no-comment">暂无评论,快去评论吧</div>
) : (
<ul>
{this.state.comments.map((item) => (
<li key={item.id}>
<h3>评论人:{item.name}</h3>
<p>评论内容:{item.content}</p>
</li>
))}
</ul>
)} */}
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
组件进阶
组件通讯
默认情况下,组件是封闭的,一个完整的功能拆分为多个组件,更好的完成整个应用功能,多个组件共享某些数据,为了实现这些功能,就需要打破组件的封闭性,让其与外界沟通,这就是组件通讯
组件的props
- 组件是封闭性的,要接收外部数据应该通过props 来实现
props的作用:接收传递给组件的数据- 传递数据:给组件标签添加属性
- 接收数据:函数组件通过参数props接收数据,类组件通过this.props接收数据
1.函数组件的props
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
const Hello = (props) => {
console.log(props); // 是个对象
return (
<div>
<h1>props: {props.name}</h1>
<h1>年龄:{props.age}</h1>
</div>
);
};
// 渲染
ReactDOM.render(
<Hello name="jack" age={19} />,
document.querySelector('#root')
);
2.类组件props
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
class Hello extends React.Component {
render() {
console.log(this.props);
return (
<div>
<h1>props:{this.props.name}</h1>
<h1>年龄:{this.props.age}</h1>
</div>
);
}
}
// 渲染
ReactDOM.render(
<Hello name="jack" age={19} />,
document.querySelector('#root')
);
特点:
-
只能读取
-
可以传多个参数任意类型,是一个对象
-
注意:使用类组件时,写了构造函数,应该将props 传递给 super(),否则,无法在构造函数中获取到props
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
class Hello extends React.Component {
// 推荐使用
constructor(props) {
super(props);
}
render() {
return <div>接收到的数据:{this.props.age}</div>;
}
}
// 渲染
ReactDOM.render(
<Hello name="jack" age={19} />,
document.querySelector('#root')
);
组件通讯的三种方式
1.父组件到子组件
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
class Parent extends React.Component {
constructor(props) {
super(props)
}
state = {
lastName: '王',
};
render() {
return (
<div className="parent">
父组件:
<Child name={this.state.lastName} props={this.props} />
</div>
);
}
}
const Child = (props) => {
console.log(props);
return (
<div className="child">
<p>子组件,接收到父组件的数据:{props.name}</p>
</div>
);
};
// 渲染
ReactDOM.render(
<Parent name="jack" age={19} />,
document.querySelector('#root')
);
2.子传父组件
利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
class Parent extends React.Component {
state = {
msg: '',
};
// 提供回调函数,用来接收数据
getChilMsg = (data) => {
console.log('接收到子度之间传递过来的数据:', data);
this.setState({
msg: data,
});
};
render() {
return (
<div className="parent">
父组件: {this.state.msg}
<Child getMsg={this.getChilMsg} />
</div>
);
}
}
class Child extends React.Component {
state = {
msg: 'hello',
};
//
handleClick = () => {
this.props.getMsg(this.state.msg);
};
render() {
return (
<div className="child">
<p>
子组件,接收到父组件的数据:{' '}
<button onClick={this.handleClick}>点我给父组件传递数据</button>
</p>
</div>
);
}
}
// 渲染
ReactDOM.render(
<Parent name="jack" age={19} />,
document.querySelector('#root')
);
3.兄弟组件通信
- 将共享状态提升到公共父组件中,由公共组件管理这个状态
- 思想:状态提升
- 公共父组件职责:1.提供共享状态 2.提供操作方法
- 要通讯的子组件只需通过 props 接收状态或操作状态的方法
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
class Counter extends React.Component {
state = {
count: 1,
};
addCount = () => {
const { count } = this.state;
this.setState({
count: count + 1,
});
};
render() {
return (
<div>
<Child1 count={this.state.count} />
<Child2 count={this.state.count} addCount={this.addCount} />
</div>
);
}
}
const Child1 = (props) => {
return <h1>计数器:{props.count}</h1>;
};
const Child2 = (props) => {
return <button onClick={() => props.addCount()}>+1</button>;
};
// 渲染
ReactDOM.render(<Counter />, document.querySelector('#root'));
Context
作用:跨组件传递函数
使用步骤:
1.调用React.createContext() 创建 Provider(提供数据)和Consumer(消费数据)两个组件
2.Provider 作为父节点
3.设置value属性,表示要传递的数据
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
// 创建context得到两个组件
const { Provider, Consumer } = React.createContext();
class App extends React.Component {
render() {
return (
<Provider value="pink">
<div>
<Child1 />
</div>
</Provider>
);
}
}
const Child1 = () => {
return <Child2></Child2>;
};
const Child2 = () => {
return (
<div>
<Consumer>{(data) => <span>{data}</span>}</Consumer>
<h1>我是最里面的子组件</h1>
</div>
);
};
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
props深入
- children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
- children属性与普通的props一样,值可以是任意值(文本、React元素、组件、函数)
import * as React from 'react';
import ReactDOM from 'react-dom';
/**
* props
*/
const App = (props) => {
console.log(props);
return (
<div>
<h1>组件的子节点:{props.children}</h1>
</div>
);
};
// 渲染
ReactDOM.render(<App>我是子节点</App>, document.querySelector('#root'));
钩子函数
1.创建时
constructor -> render -> componentDidMount
import * as React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props);
console.log('constructor'); // 初始化state this指向问题
this.handleCLick = this.handleCLick.bind(this);
this.state = {
count: 0,
};
}
componentDidMount() {
const title = document.querySelector('#title');
console.log(title);
console.log('componentDidMount'); // 发送网络请求 DOM操作
}
handleCLick() {
this.setState({
count: this.state.count + 1,
});
}
render() {
console.log('render'); // 渲染UI 创建和更新时执行 (不能调用setState)
return (
<div>
<h1 id="title">次数:{this.state.count} </h1>
<button onClick={this.handleCLick}>打豆豆</button>
</div>
);
}
}
// 渲染
ReactDOM.render(
<App colors={['red', 'blue']} />,
document.querySelector('#root')
);
2.更新时
触发的三种方式
- 执行机制:1.setState() 2.forceUpdate() 3.接收新的props
- render -> componentDidUpdate
import * as React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props);
this.handleCLick = this.handleCLick.bind(this);
this.state = {
count: 0,
};
}
handleCLick() {
this.setState({
count: this.state.count + 1,
});
/**
* 3.点击按钮触发 forceUpdate
*/
this.forceUpdate();
}
// 更新阶段 触发的三种方式
render() {
/**
* 1.调用setState
*/
console.log('render');
return (
<div>
<Counter count={this.state.count} />
<button onClick={this.handleCLick}>打豆豆</button>
</div>
);
}
}
class Counter extends React.Component {
constructor(props) {
super(props);
console.log('constructor');
}
render() {
/**
* 2.调用newProps 接收新属性
*/
console.log('子组件触发了render');
const { count } = this.props;
return <h1>次数:{count}</h1>;
}
componentDidUpdate(prevProps) {
console.log('触发了componentDidMount', prevProps);
/**
* 注意:
* 在这里使用setState 要加if
* 比较前后的props 是否相同 prevProps 上一次的props this.props 当前的props
*/
if (prevProps.count !== this.props.count) {
this.setState({});
}
}
// 渲染
ReactDOM.render(
<App colors={['red', 'blue']} />,
document.querySelector('#root')
);
3.卸载时
- 执行机制:组件从页面消失
- 做一些清理工作
import * as React from 'react';
import ReactDOM from 'react-dom';
class App extends React.Component {
constructor(props) {
super(props);
this.handleCLick = this.handleCLick.bind(this);
this.state = {
count: 0,
};
}
handleCLick() {
this.setState({
count: this.state.count + 1,
});
this.forceUpdate();
}
render() {
console.log('render');
return (
<div>
{this.state.count > 3 ? (
<div>豆豆被打死了</div>
) : (
<Counter count={this.state.count} />
)}
<button onClick={this.handleCLick}>打豆豆</button>
</div>
);
}
}
class Counter extends React.Component {
constructor(props) {
super(props);
console.log('constructor');
}
componentWillMount() {
this.timerId = setInterval(() => {
console.log('定时器执行');
}, 500);
}
render() {
const { count } = this.props;
return <h1>次数:{count}</h1>;
}
componentWillUnmount() {
console.warn('componentWillUnmount');
// 清理定时器
clearInterval(this.timerId);
}
}
// 渲染
ReactDOM.render(
<App colors={['red', 'blue']} />,
document.querySelector('#root')
);
rnder-porps和高阶组件
- 思考:如果两个组件中的部分功能相似或相同,该如何处理
- 处理方式: 复用相似的功能(联想函数封装)
- 复用什么?1. state 2. 操作state的方法 (组件状态逻辑)
- 两种方式:1.render props 模式 2.高阶组件(HOC)
- 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧。烟花而成的固定模式(写法)
render props 模式
-
思路:将复用的state和操作state的方法封装到一个组件中
-
问题1:如何拿到该组件中复用的state?
-
在使用组件时,添加一个值为函数的props,通过 函数参数 来获取(需要组件内部实现)
-
问题2:如何渲染任意的UI
-
使用该函数的返回值作为要渲染的UI内容(需要组件内部实现)
使用步骤
- 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态2.操作方法)
- 将要复 用的状态作为 props.render(state)方法的参数,暴露到组件外部
- 使用props.render() 返回值作为要渲染的内容
import * as React from 'react';
import ReactDOM from 'react-dom';
class Mouse extends React.Component {
state = {
x: 0,
y: 0,
};
// 鼠标移动事件的事件处理程序
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
});
};
// 监听鼠标移动事件
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove);
}
render() {
return this.props.render(this.state);
}
}
class App extends React.Component {
render() {
return (
<div>
<h1>render props 模式</h1>
<Mouse
render={(mouse) => {
return (
<p>
鼠标位置:{mouse.x} {mouse.y}
</p>
);
}}
/>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
优化
import * as React from 'react';
import ReactDOM from 'react-dom';
import typeProps from 'prop-types';
import img from './images/logo192.png';
class Mouse extends React.Component {
state = {
x: 0,
y: 0,
};
// 鼠标移动事件的事件处理程序
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
});
};
// 监听鼠标移动事件
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove);
}
render() {
return this.props.children(this.state);
}
// 移除手动事件
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove);
}
}
Mouse.typeProps = {
children: typeProps.func.isRequired,
};
class App extends React.Component {
render() {
return (
<div>
<h1>render props 模式</h1>
{/* <Mouse
render={(mouse) => {
return (
<p>
鼠标位置:{mouse.x} {mouse.y}
</p>
);
}}
/> */}
<Mouse>
{(mouse) => {
return (
<p>
鼠标位置:{mouse.x} {mouse.y}
</p>
);
}}
</Mouse>
{/* 跟随鼠标移动的图片 */}
{/* <Mouse
render={(mouse) => {
return (
<img
src={img}
alt="react"
style={{
position: 'absolute',
left: mouse.x - 96,
top: mouse.y - 96,
}}
/>
);
}}
/> */}
<Mouse>
{(mouse) => (
<img
src={img}
alt="react"
style={{
position: 'absolute',
left: mouse.x - 96,
top: mouse.y - 96,
}}
/>
)}
</Mouse>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
高阶组件
- 目的:实现状态逻辑复用
- 采用包装模式,比如说:手机壳
- 手机:获取保护功能
- 手机壳:提供保护功能
- 高阶组件就相当于手机壳,通过包装组件,增强组件功能
思路分析
- 高阶组件是一个函数,接收要包装的组件,但会增加后的组件
- 高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件的WrapperComponent
使用步骤
- 创建一个函数,名称约定以 with开头
- 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
- 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
- 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
- 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
import * as React from 'react';
import ReactDOM from 'react-dom';
import typeProps, { func } from 'prop-types';
import img from './images/logo192.png';
// 创建高阶组件
function withMouse(WrappedComponrnt) {
// 该组件提供复用的状态逻辑
class Mouse extends React.Component {
state = {
x: 0,
y: 0,
};
// 鼠标移动事件的事件处理程序
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
});
};
// 监听鼠标移动事件
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove);
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove);
}
render() {
return <WrappedComponrnt {...this.state}></WrappedComponrnt>;
}
}
return Mouse;
}
// 用来测试高阶组件
const Position = (props) => (
<p>
鼠标位置:{props.x} {props.y}
</p>
);
// 获取增强后的组件
const MousePosition = withMouse(Position);
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
{/* 增强后的组件 */}
<MousePosition></MousePosition>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
设置displayNama
- 使用高阶组件存在的问题:得到的两个组件名称相同
- 原因:默认情况下,React使用组件名称作为displayName
- 解决方式:为高阶组件设置displayName 便于调试时区分不同的组件
- displayName的作用:用于设置调试信息(React Developer Tools信息)
- 设置方式:
// 设置displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponrnt)}`;
function getDisplayName(WrappedComponrnt) {
return WrappedComponrnt.displayName || WrappedComponrnt.name || 'Component';
}
传递props
- 问题:props丢失
- 原因:高阶组件没有往下传递props
- 解决方式:渲染WrappedComponent,将state 和 this.props 一起传递给组件
render() {
return <WrappedComponrnt {...this.state} {...this.props} />;
}
完整代码
import * as React from 'react';
import ReactDOM from 'react-dom';
// import typeProps, { func } from 'prop-types';
// import img from './images/logo192.png';
// 创建高阶组件
function withMouse(WrappedComponrnt) {
// 该组件提供复用的状态逻辑
class Mouse extends React.Component {
state = {
x: 0,
y: 0,
};
// 鼠标移动事件的事件处理程序
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY,
});
};
// 监听鼠标移动事件
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove);
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove);
}
render() {
return <WrappedComponrnt {...this.state} {...this.props} />;
}
}
// 设置displayName
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponrnt)}`;
return Mouse;
}
function getDisplayName(WrappedComponrnt) {
return WrappedComponrnt.displayName || WrappedComponrnt.name || 'Component';
}
// 用来测试高阶组件
const Position = (props) => {
console.log('props,', props);
return (
<p>
鼠标位置:{props.x} {props.y}
</p>
);
};
// 获取增强后的组件
const MousePosition = withMouse(Position);
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
{/* 增强后的组件 */}
<MousePosition a="1"></MousePosition>
</div>
);
}
}
// 渲染
ReactDOM.render(<App />, document.querySelector('#root'));
总结
- 组件通讯是构建 react 应用必不可少
- props的灵活性让组件更加强大
- 状态提升是 react组件的常用模式
- 组件生命周期有助于理解组件的运行过程
- 钩子函数让开发者可以在特定的时机执行某些功能
- render props 模式 和 高阶组件都可以实现组件状态逻辑复用
- 组件极简模型:(state,props)=> UI