React 全家桶
npm i -g create-react-app
create-react-app myapp
npx create-react-app myapp
ReactDOM.render(
<div id="aaa" style={{ color: 'red' }}>
<p id="bbb">111</p>
<p id="ccc">222</p>
</div>,
document.getElementById('root')
);
ReactDOM.render(
React.createElement(
'div',
{
id: 'aaa',
style: {
color: 'red',
},
},
[
React.createElement('p', { id: 'bbb' }, 111),
React.createElement('p', { id: 'ccc' }, 222),
]
),
document.getElementById('root')
);
import React from 'react';
class MyApp extends React.Component {
render() {
return <div>111</div>;
}
}
export default MyApp;
function MyApp() {
return <div>11---1</div>;
}
const MyApp2 = () => <div>MyApp</div>;
export default MyApp2;
import React, { Component } from 'react';
import './css/01-index.css';
class MyApp extends Component {
render() {
const style = {
background: 'red',
width: 200,
textAlign: 'center',
color: '#fff',
};
return (
<div>
<p
style={{
background: 'green',
width: 200,
textAlign: 'center',
color: 'white',
}}
>
第一种展示样式方法
</p>
<p style={style}>第二种展示样式方法</p>
<p className="highlight">
第三种展示样式方法,通过引入外部样式,webpack把样式当做head标签内的内部样式执行了
</p>
</div>
);
}
}
export default MyApp;
// 访问dom节点的值
myref = React.createRef();
<input ref={this.myref} id="input2" />;
// 访问 this.myref.current.value
console.log(this.myref.current.value);
// 获取子组件的实例,通过实例调用子组件上的方法
import React, { Component } from 'react';
class App extends Component {
myRef = React.createRef();
render() {
return (
<div>
<button
onClick={() => {
const childRef = this.myRef.current;
childRef.test();
console.log(this.myRef.current); // 子组件的实例
}}
>
click
</button>
<MyInput ref={this.myRef} />
</div>
);
}
}
class MyInput extends Component {
test() {
console.log('test');
}
render() {
return (
<div>
<input defaultValue={20} />
</div>
);
}
}
export default App;
import React, { Component } from 'react';
class MyApp extends Component {
state = {
text: '文本',
};
handleClick1 = () => {
console.log(this.state.text + '1');
};
handleClick3() {
console.log(this.state.text + '3');
}
handleClick4() {
console.log(this.state.text + '4');
}
render() {
return (
<div>
<button onClick={this.handleClick1}>点击1</button>
<button
onClick={() => {
console.log(this.state.text + '2');
}}
>
点击2
</button>
<button onClick={this.handleClick3.bind(this)}>点击3</button>
<button onClick={() => this.handleClick4()}>点击4</button>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class MyApp extends Component {
state = {
isShow: true,
};
render() {
const { isShow } = this.state;
return (
<div>
<h1>欢迎来到react的世界</h1>
<button
onClick={() => {
this.setState({
isShow: !isShow,
});
}}
>
{isShow ? '收藏' : '取消收藏'}
</button>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class MyApp extends Component {
constructor() {
super();
this.state = {
arr: [
{
id: 1,
text: '111',
},
{
id: 2,
text: '222',
},
{
id: 3,
text: '333',
},
],
};
}
render() {
const { arr } = this.state;
return (
<div>
<ul>
{arr.map((item) => {
return <li key={item.id}>{item.text}</li>;
})}
</ul>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class MyApp extends Component {
constructor() {
super();
this.state = {
myHtml: `<b style="color: red;">111</b>`,
inputHtml: '',
};
}
myRef = React.createRef();
handleOk = () => {
const inputValue = this.myRef.current.value;
this.setState({
inputHtml: inputValue,
});
};
render() {
const { myHtml, inputHtml } = this.state;
return (
<div>
<div
dangerouslySetInnerHTML={{
__html: myHtml,
}}
></div>
<div>
<input ref={this.myRef} />
<button onClick={this.handleOk}>确定</button>
</div>
<div
dangerouslySetInnerHTML={{
__html: inputHtml,
}}
></div>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class MyApp extends Component {
state = {
count: 0,
};
handleClick1 = () => {
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log(this.state.count, '1()'); // 4
}
);
console.log(this.state.count, '1'); // 1-先执行,后面的按序号
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log(this.state.count, '2()'); // 5
}
);
console.log(this.state.count, '2'); // 2
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log(this.state.count, '3()'); // 6
}
);
console.log(this.state.count, '3'); // 3
};
handleClick2 = () => {
// setTimeout是异步的
setTimeout(() => {
this.setState({
count: this.state.count + 1,
});
console.log(this.state.count, '1'); // 1-先执行,后面的按序号
this.setState({
count: this.state.count + 1,
});
console.log(this.state.count, '2'); // 2
this.setState({
count: this.state.count + 1,
});
console.log(this.state.count, '3'); // 3
}, 0);
};
render() {
return (
<div>
{this.state.count}
<button onClick={this.handleClick1}>点击1</button>
<button onClick={this.handleClick2}>点击2</button>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class Navbar extends Component {
a = 1; // 对象属性,需要实例化对象,访问的时候需要new一个对象 let obj = new Navbar(); obj.a
// 把类属性挪到里面,要加static表示它是类属性,不需要实例化,访问的时候Navbar.propTypes
static propTypes = {
title: PropTypes.string,
leftBtn: PropTypes.bool,
rightBtn: PropTypes.bool,
};
static defaultProps = {
leftBtn: true,
rightBtn: true,
};
render() {
const { title, leftBtn, rightBtn } = this.props;
return (
<div>
{leftBtn && <button>返回</button>}
<span>{title}</span>
{rightBtn && <button>搜索</button>}
</div>
);
}
}
export default Navbar;
// 类属性
// Navbar.propTypes = {
// title: PropTypes.string,
// leftBtn: PropTypes.bool,
// rightBtn: PropTypes.bool
// };
// 默认属性
// Navbar.defaultProps = {
// leftBtn: true,
// rightBtn: true
// };
import React from 'react';
import PropTypes from 'prop-types';
export default function SiderBar(props) {
const { background, position } = props;
const obj = {
background,
width: 200,
height: 200,
position: 'fixed',
top: 30,
};
const obj1 = { left: 0 };
const obj2 = { right: 0 };
const styleObj =
position === 'left' ? { ...obj, ...obj1 } : { ...obj, ...obj2 };
return (
<div>
<ul style={styleObj}>
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
<li>555</li>
<li>666</li>
</ul>
</div>
);
}
// 函数组件只能通过类属性来定义类型
SiderBar.propTypes = {
background: PropTypes.string,
position: PropTypes.string,
};
SiderBar.defaultProps = {
background: '',
position: '',
};
<4> 特殊属性:children-插槽,在父组件文件中引入子组件标签的内部再插入 jsx,此时需要在子组件预留一个位置显示 children,children 是个数组。插槽一是为了复用,二是一定程度的减少父子通信
import React, { Component } from 'react';
// 点击展开收起让SiderBar显示隐藏,把button当成插槽放到父组件中去
class Navbar extends Component {
render() {
return (
<div style={{ background: 'yellow' }}>
{this.props.children}
Navbar
</div>
);
}
}
class SiderBar extends Component {
render() {
return (
<div
style={{
background: '#fcc',
width: 300,
height: 300,
display: this.props.isShow ? 'block' : 'none',
}}
>
SiderBar
</div>
);
}
}
class MyApp extends Component {
state = {
isShow: false,
};
toggleShow = () => {
this.setState({
isShow: !this.state.isShow,
});
};
render() {
const { isShow } = this.state;
return (
<div>
<Navbar>
<button
onClick={() => {
this.toggleShow();
}}
>
展开收起
</button>
</Navbar>
<SiderBar isShow={isShow} />
</div>
);
}
}
export default MyApp;
<1> 表单的受控:1.靠修改 state 的值引起 render 重新渲染实现。通过表单组件的 value 属性以及 onChange 事件,value 的值由 state 控制,调用 onChange 去修改 state 的值,setState 每次改变都会触发 render 重新渲染,所以表单组件的 value 值能确保是最新的。2.也可以靠 ref 获取子组件的实例,获取子组件 state 的值以及修改值
原生的 input 标签跟 React 中的 input 标签有什么区别?1.原生 input 标签上的监听输入框实时改变的事件是 oninput,React 中监听输入框实时改变的事件是 onChange
<3> 组件的受控:组件的数据渲染应该由被调用者传递的 props 完全控制,控制则为受控组件,否则是非受控组件。多写无状态组件,少写有状态组件。组件尽量不要有自己的状态,状态应该由传过来的 props 完全控制。
import React, { Component } from 'react';
class MyApp extends Component {
state = {
username: 'xiaoming',
};
render() {
const { username } = this.state;
return (
<div>
<input
value={username}
type="text"
onChange={(evt) => {
this.setState({
username: evt.target.value,
});
}}
/>
<button
onClick={() => {
console.log(username);
}}
>
登录
</button>
<button
onClick={() => {
this.setState({
username: '',
});
}}
>
重置
</button>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
// 点击按钮让SiderBar显示隐藏
class NavBar extends Component {
render() {
const { event } = this.props;
return (
<div style={{ background: 'green' }}>
<button
onClick={() => {
event();
}}
>
按钮
</button>
<span>NavBar</span>
</div>
);
}
}
class SiderBar extends Component {
render() {
return (
<ul style={{ background: 'yellow', width: 200 }}>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
);
}
}
class MyApp extends Component {
state = {
isShow: true,
};
handleEvent = () => {
this.setState({
isShow: !this.state.isShow,
});
};
render() {
const { isShow } = this.state;
return (
<div>
<NavBar event={this.handleEvent} />
{isShow && <SiderBar />}
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
import axios from 'axios';
import './css/兄弟组件通信.css';
// FilmItem 与 FilmDetail通信
class MyApp extends Component {
state = {
filmList: [],
info: '',
};
componentDidMount() {
axios({
method: 'get',
url:
'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=764626',
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929"}',
'X-Host': 'mall.film-ticket.film.list',
},
}).then((res) => {
this.setState({
filmList: res.data.data.films,
});
});
}
render() {
return (
<div className="app">
<div>
{this.state.filmList.map((item) => (
<FilmItem
key={item.filmId}
{...item}
onEventChange={(value) => {
this.setState({
info: value,
});
}}
/>
))}
</div>
<FilmDetail info={this.state.info} />
</div>
);
}
}
export default MyApp;
const FilmItem = (props) => {
const { name, poster, actors, synopsis, onEventChange = () => {} } = props;
return (
<div
className="filmItem"
onClick={() => {
onEventChange(synopsis);
}}
>
<img src={poster} />
<div>
<p>{name}</p>
<p>主演:{actors.map((item) => item.name).join(' ')}</p>
</div>
</div>
);
};
function FilmDetail(props) {
return <div className="filmDetail">{props.info || '--'}</div>;
}
import React, { Component } from 'react';
export default class App extends Component {
childRef = React.createRef();
handleClick = () => {
const childInstance = this.childRef.current;
childInstance.test(); // 父组件上调用子组件的方法,实现通信
};
render() {
return (
<div>
<Child ref={this.childRef} />
<button onClick={this.handleClick}>click</button>
</div>
);
}
}
class Child extends Component {
test() {
console.log('test');
}
render() {
return <div>Child</div>;
}
}
import React, { Component } from 'react';
import axios from 'axios';
import './css/兄弟组件通信.css';
const bus = {
list: [],
// 订阅
subscribe(callback) {
// console.log(callback);
this.list.push(callback);
},
// 发布
publish(text) {
// 遍历所有的list,将回调函数执行
this.list.forEach((callback) => {
callback && callback(text);
});
},
};
// FilmItem 与 FilmDetail通信
class MyApp extends Component {
state = {
filmList: [],
};
componentDidMount() {
axios({
method: 'get',
url:
'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=764626',
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929"}',
'X-Host': 'mall.film-ticket.film.list',
},
}).then((res) => {
this.setState({
filmList: res.data.data.films,
});
});
}
render() {
return (
<div className="app">
<div>
{this.state.filmList.map((item) => (
<FilmItem key={item.filmId} {...item} />
))}
</div>
<FilmDetail />
</div>
);
}
}
export default MyApp;
const FilmItem = (props) => {
const { name, poster, actors, synopsis } = props;
return (
<div
className="filmItem"
onClick={() => {
bus.publish(synopsis);
}}
>
<img src={poster} alt={name} />
<div>
<p>{name}</p>
<p>主演:{actors.map((item) => item.name).join(' ')}</p>
</div>
</div>
);
};
class FilmDetail extends Component {
state = {
info: '',
};
// 组件一上来就要订阅
componentDidMount() {
bus.subscribe((value) => {
this.setState({
info: value,
});
});
}
render() {
return <div className="filmDetail">{this.state.info || '--'}</div>;
}
}
#### context 通信模式:生产者消费者模式
#### 1.把父组件当成生产者,使用 GlobalContext.Provider 包裹起来,传递 value 属性
#### 2.想要通信的子组件当成消费者,使用 GlobalContext.Consumer 包裹起来,里面使用回调函数取的共享状态的 value 属性,共享父组件的 value 属性
import React, { Component } from 'react';
import axios from 'axios';
import './css/兄弟组件通信.css';
const GlobalContext = React.createContext(); // 创建一个生产者-供应商
// FilmItem 与 FilmDetail通信
class MyApp extends Component {
state = {
filmList: [],
info: '',
};
componentDidMount() {
axios({
method: 'get',
url:
'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=764626',
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929"}',
'X-Host': 'mall.film-ticket.film.list',
},
}).then((res) => {
this.setState({
filmList: res.data.data.films,
});
});
}
render() {
return (
<div className="app">
<GlobalContext.Provider
value={{
info: this.state.info,
changeInfo: (value) => {
this.setState({
info: value,
});
},
}}
>
<div>
{this.state.filmList.map((item) => (
<FilmItem key={item.filmId} {...item} />
))}
</div>
<FilmDetail />
</GlobalContext.Provider>
</div>
);
}
}
export default MyApp;
const FilmItem = (props) => {
const { name, poster, actors, synopsis } = props;
return (
<GlobalContext.Consumer>
{(value) => {
return (
<div
className="filmItem"
onClick={() => {
value.changeInfo(synopsis);
}}
>
<img src={poster} />
<div>
<p>{name}</p>
<p>主演:{actors.map((item) => item.name).join(' ')}</p>
</div>
</div>
);
}}
</GlobalContext.Consumer>
);
};
function FilmDetail() {
return (
<GlobalContext.Consumer>
{(value) => {
// console.log(value);
return <div className="filmDetail">{value.info ?? '--'}</div>;
}}
</GlobalContext.Consumer>
);
}
d.componentDidUpdate:获取更新后的 dom 节点。缺点是:会执行多次,添加标志位进行判断。componentDidUpdate 有 2 个参数,prevProps 和 prevState,老的属性和老的状态
import React, { Component } from 'react';
import axios from 'axios';
import BetterScroll from 'better-scroll';
class MyApp extends Component {
state = {
myname: 'qiu',
filmList: [],
};
componentDidMount() {
axios({
method: 'get',
url:
'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=764626',
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929"}',
'X-Host': 'mall.film-ticket.film.list',
},
}).then((res) => {
this.setState(
{
filmList: res.data.data.films,
}
// , () => {
// console.log(document.getElementById("wrapper"));
// new BetterScroll("#wrapper");
// }
);
});
}
UNSAFE_componentWillUpdate() {
console.log(
'更新之前的state状态',
document.getElementById('myname').innerHTML
); //qiu
}
componentDidUpdate(prevProps, prevState) {
// 更新后,想要获取dom节点
// 缺点:会执行多次,添加标志位-使用老的属性或状态去判断,避免重复执行
console.log(
'更新之后的state状态',
document.getElementById('myname').innerHTML
); //QIU
// console.log(prevState); // 上一次的state,只有第一次的时候filmList是空的
if (!prevState.filmList.length) {
new BetterScroll('#wrapper');
}
}
render() {
return (
<div>
<button
onClick={() => {
this.setState({
myname: 'QIU',
});
}}
>
click
</button>
<p id="myname">{this.state.myname}</p>
<div
id="wrapper"
style={{
height: 100,
cursor: 'pointer',
background: 'yellow',
overflow: 'hidden',
userSelect: 'none',
}}
>
<ul>
{this.state.filmList.map((item) => {
return <li key={item.filmId}>{item.name}</li>;
})}
</ul>
</div>
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class MyApp extends Component {
state = {
myname: 'qiu',
};
UNSAFE_componentWillUpdate() {
console.log('UNSAFE_componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
shouldComponentUpdate = (nextProps, nextState) => {
// 该案例中已经点击了click把myname改为QIU,但是每次再次点击还是会执行生命周期以及render重新渲染
// console.log(this.state, nextState);
if (JSON.stringify(this.state) !== JSON.stringify(nextState)) {
return true;
}
return false;
};
render() {
console.log('render');
return (
<div>
<button
onClick={() => {
this.setState({
myname: 'QIU',
});
}}
>
click
</button>
{this.state.myname}
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class Child extends Component {
state = {
num: 0,
};
timer = null;
componentDidMount() {
// 窗口的变化
window.onresize = () => {
console.log('resize');
};
// 定时器
this.timer = setInterval(() => {
console.log('setInterval');
this.setState((prevState) => ({
num: prevState.num + 1,
}));
}, 1000);
}
componentWillUnmount() {
console.log('componentWillUnmount');
// 清除window上挂载的事件
window.onresize = null;
// 清除定时器
clearInterval(this.timer);
}
render() {
return <div>Child {this.state.num}</div>;
}
}
class MyApp extends Component {
state = {
isShow: true,
};
render() {
return (
<div>
<button
onClick={() => {
this.setState({
isShow: !this.state.isShow,
});
}}
>
click
</button>
{this.state.isShow && <Child />}
</div>
);
}
}
export default MyApp;
a. getDerivedStateFromProps:从属性中获取衍生的状态,把属性转换为状态。第一次初始化,自己更新 state,父传子改变 state,都会触发这个生命周期。类属性,this 指向 undefined,没有 this.state
import React, { Component } from 'react';
class MyApp extends Component {
state = {
myname: 'qiu',
};
// 自己更新,父组件更新,初始化,都会触发这个生命周期执行
// 从属性中获得衍生的状态
// return一个对象,return的结果会与state进行合并覆盖,配合componentDidUpdate进行异步请求
// nextProps-最新的属性 nextState-最新的状态
// static:类属性,this指向undefined,没有this.state
static getDerivedStateFromProps(nextProps, nextState) {
console.log('getDerivedStateFromProps', nextState);
return {
myname:
nextState.myname.substring(0, 1).toUpperCase() +
nextState.myname.substring(1),
};
}
render() {
return (
<div>
<button
onClick={() => {
this.setState({
myname: 'kkk',
});
}}
>
click
</button>
{this.state.myname}
</div>
);
}
}
export default MyApp;
import React, { Component } from 'react';
class MyApp extends Component {
state = {
myname: 'qiu',
};
componentDidUpdate(prevProps, prevState, value) {
// 该value是getSnapshotBeforeUpdate返回的值
console.log('componentDidUpdate', value);
}
// 在更新之前记录下快照,返回一个值,
// render -> getSnapshotBeforeUpdate -> componentDidUpdate
getSnapshotBeforeUpdate() {
console.log('getSnapshotBeforeUpdate');
return {
num: 100,
};
}
render() {
console.log('render');
return (
<div>
<button
onClick={() => {
this.setState({
myname: 'QIU',
});
}}
>
click
</button>
MyApp-{this.state.myname}
</div>
);
}
}
export default MyApp;
const [list, setlist] = useState([]);
每次 useState 更新,函数都会重新创建一次,所以每次定义的函数都会重新定义一遍,很消耗性能。useCallback 是让跟依赖项不相关的 useState 改变时,返回的函数是缓存中的函数,如果依赖项改变,useCallback 是需要重新创建的。
useMemo 相当于 vue 的计算属性,返回一个结果,返回的结果可以是任意类型。useMemo 也是记忆组件,缓存数据的作用,当依赖项改变时,重新创建新的函数计算;若依赖项没改变,从缓存中读取数据。useMemo 这段函数比 useEffect 还先执行,所以 useMemo 内取的 state 值一开始都是初始化的值
const getDataList = useMemo(() => [1, 2, 3], []);
{
getDataList.map((item) => <p key={item}>{item}</p>);
}
const myRef = useRef();
<input ref={myRef} />
<button onClick={() => {
console.log(myRef.current.value);
}}>click</button>
const countRef = useRef(0);
<p>{countRef.current}</p>;
<button
onClick={() => {
countRef.current++;
}}
>
change-count
</button>;
- 1.创建共享中心:const GlobalContext = React.createContext();
- 2.把父组件当生产者,需要传的值放在 value 属性里
<GlobalContext.Provider
value={{
detailInfo,
setdetailInfo,
}}
>
<Child />
</GlobalContext.Provider>
- 3.把子组件当消费者:const value = useContext(GlobalContext);
useReducer 把状态全部抽离出去外面管理,降低组件内数据层与视图层的耦合度。先定义好 initialState 和 reducer 函数,useReducer 只能写在父组件上,然后结合 useContext 把状态和触发函数传递给子组件。
import React, { useContext, useEffect, useReducer } from 'react';
import axios from 'axios';
const GlobalContext = React.createContext();
const initialState = {
filmList: [],
detailInfo: '',
};
const reducer = (prevState, action) => {
const newState = { ...prevState };
switch (action.type) {
case 'setFilmList':
newState.filmList = action.value;
return newState;
case 'setDetailInfo':
newState.detailInfo = action.value;
return newState;
default:
return prevState;
}
};
export default function MyApp() {
const [state, dispatch] = useReducer(reducer, initialState);
/**
* useReducer不能实现异步请求,redux可以,依靠中间件
*/
useEffect(() => {
axios({
method: 'get',
url:
'https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=2&k=764626',
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929"}',
'X-Host': 'mall.film-ticket.film.list',
},
}).then((res) => {
dispatch({
type: 'setFilmList',
value: res.data.data.films,
});
});
}, []);
return (
<GlobalContext.Provider
value={{
state,
dispatch,
}}
>
<div>
{state.filmList.map((item) => (
<FilmItem key={item.filmId} {...item} />
))}
<FilmDetail />
</div>
</GlobalContext.Provider>
);
}
const FilmItem = ({ name, poster, synopsis }) => {
const { dispatch } = useContext(GlobalContext);
return (
<div
style={{ marginBottom: 10 }}
onClick={() =>
dispatch({
type: 'setDetailInfo',
value: synopsis,
})
}
>
<img
src={poster}
alt={name}
style={{
width: 100,
height: 100,
marginRight: 10,
cursor: 'pointer',
}}
/>
<span>电影名称:{name}</span>
</div>
);
};
const FilmDetail = () => {
const { state } = useContext(GlobalContext);
return (
<div
style={{
background: 'yellow',
width: 300,
height: 300,
position: 'fixed',
right: 0,
top: 0,
}}
>
FilmDetail-{state.detailInfo}
</div>
);
};
npm i react-router-dom@5 -S
<BrowserRouter>
{/* 匹配一级路由,若是嵌套路由,需写在组件内部 */}
<Switch>
<Route path="/films" component={Films}></Route>
<Route path="/cinemas" component={Cinemas}></Route>
<Route path="/mine" component={Mine}></Route>
{/* 动态路由 */}
<Route path="/filmdetail/:id" component={FilmDetail} />
<Redirect from="/" to="/films" exact />
<Route component={NotFound} />
</Switch>
{/* 留好插槽给TabBar组件 */}
{props.children}
</BrowserRouter>
2. Redirect:路由重定向:"/" 自动跳转到 "/films",exact 参数表示是否精确匹配的意思,如果不精确匹配,所有的 path 路径都符合"/"开头,那么走到这里后面的代码就不走了
// 路由配置
<Route path="/filmdetail/:id" component={FilmDetail} />;
// 跳转
const handleClick = () => {
history.push(`/filmdetail/${item.filmId}`);
};
// 获取动态id
props.match.params.id;
// 路由配置
<Route path="/filmdetail" component={FilmDetail} />;
// 跳转
const handleClick = () => {
history.push({
pathname: '/filmdetail',
query: {
id: item.filmId,
},
});
};
// 获取动态id
props?.location?.query?.id;
// 路由配置
<Route path="/filmdetail" component={FilmDetail} />;
// 跳转
const handleClick = () => {
history.push({
pathname: '/filmdetail',
state: {
id: item.filmId,
},
});
};
// 获取动态id
props?.location?.state?.id;
const isAuth = () => localStorage.getItem('token');
<Route
path="/mine"
render={(props) => {
// 把组件<Login/>实例化使用,相当于new,组件内部是没有props对象的,在回调参数内把props手动传递下去,组件才会含有history属性
// 如果不在回调参数内把props手动传递下去,React提供了一个高阶组件给任何组件使用,withRouter让被包裹的组件含有history属性
return isAuth() ? <Mine /> : <Redirect to="/login" {...props} />;
}}
/>;
Route 的 component 属性使用的组件是把组件当成 Route 的孩子,也就是把 component 属性放在 Route 的 jsx 内部了,component 组件内部自然会有父组件的 history 属性。但是如果 component 组件内部还有自己封装的子组件,这个子组件如果没有手动传递 props 属性给它,它接收到的 props 是{},所以如果子组件想要拿到 location、history 等属性,可以把 props 当属性传给子组件。也可以在子组件内使用 withRouter
function Mine(props) {
console.log(props); // props内就包含了withRouter给的location,history等属性
return <div className="mineRoot">mine</div>;
}
export default withRouter(Mine);
在开发环境中,如果请求后端的接口,后端的 ip 跟自己的 ip 不一样,这样子请求接口就会发生跨域。跨域是浏览器的同源策略决定的,不存在于服务器之间。生产环境不存在跨域,因为应用已经部署在服务器上了。
1. jsonp:利用 script 的 scr 属性没有跨域限制。前端动态创建 script 标签,src 指向后端接口路径,在路径后面添加 callback 字段,提前定义好 callback 字段的函数处理结果,插入到 body。后端接收到前端的请求,解析 callback 参数,然后给前端返回一个函数,函数名使用 callback 名,函数返回的内容为 json 格式。前端会在提前定义好的函数内接收到后端返回的数据。优点是兼容性好;缺点是只能进行 get 请求,get 请求 query 传参的大小有限
const script = document.createElement('script');
script.src = 'http://localhost:8000/list?callback=handleRes';
document.body.appendChild(script);
function handleRes(res) {
// 获取后端传递在callback的结果
console.log(res);
}
3. nginx 反向代理:在 react 项目开发中,当请求后端接口时,做一个反向代理,先向自己的本地服务器 localhost 请求,localhost 这台服务器再向后端这台真正的服务器请求数据拿回来给客户端用,所以就不存在跨域了。
react 中反向代理是利用 node 的中间件:http-proxy-middleware,src 下新建 setupProxy.js,然后重启服务:
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
'/ajax',
createProxyMiddleware({
target: 'https://i.maoyan.com',
changeOrigin: true,
})
);
};
// 请求时朝着ajax开始的路径发
useEffect(() => {
axios({
url:
'/ajax/comingList?ci=20&limit=10&movieIds=&token=&optimus_uuid=0B1360C06EC711ED9202F7A3C5B5021BF412D2EFD8344980AA9C42B64F7154DA&optimus_risk_level=71&optimus_code=10',
}).then((res) => {
console.log(res.data);
});
}, []);
React 是单页面应用,每个页面的样式都是在 head 的 style 下,所以在一个组件内给一个 div 起一个类名,在别的组件内给一个 div 起同样的类名,会应用到全局的样式,样式也会起到覆盖。
import styles from './index.module.css';
<div className={styles.content}></div>;
:global(.qiuli) {
color: orange;
}
npm i redux -S
一个组件如果要更新状态,这个状态是跟其他组件共享的。在组件内先 dispatch 一个 action 对象,触发对应的 actionCreator,把 action 对象送到 store 里面,store 不能直接更新状态,需要通过 reducer 来更新状态,reducer 接收老的状态和 action,然后基于 action 的 type 判断返回新的状态,新的状态一更新,就通知订阅者的组件来更新 state。
// 纯函数
const obj = {
name: 'qiu',
};
function test(obj) {
const newObj = { ...obj };
newObj.name === 'xiaoming';
return newObj;
}
test(obj);
const createQStore = (reducer, initialstate) => {
var list = [];
initialstate = reducer();
function subscribe(callback) {
list.push(callback);
}
function dispatch(action) {
initialstate = reducer(initialstate, action);
for (var i in list) {
list[i] && list[i]();
}
}
function getState() {
return initialstate;
}
return {
subscribe,
dispatch,
getState,
};
};
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
// 将处理不同业务的reducer拆分出来
import tabbarReducer from './reducers/tabbarReducer';
import cityReducer from './reducers/cityReducer';
import CinemaReducer from './reducers/cinemaReducer';
// npm i redux-thunk -S
import ReduxThunk from 'redux-thunk';
// npm i redux-promise -S
import ReduxPromise from 'redux-promise';
const reducer = combineReducers({
// Define a top-level state field named `todos`, handled by `todosReducer`
tabbarReducer,
cityReducer,
CinemaReducer,
});
// applyMiddleware应用中间件
const store = createStore(reducer, applyMiddleware(ReduxThunk, ReduxPromise));
export default store;
- redux-thunk:redux-thunk 允许 Action Creator 返回一个 thunk 函数而不是普通的 Action 对象。thunk 函数有两个参数,dispatch 和 getState,可以在内部执行异步代码,也可以访问 store 中的状态。
- 工作原理:在 Redux 中,把 Redux Thunk 中间件应用到 Redux Store 中,当在页面中 dispatch 一个 Action,Redux Thunk 中间件会对 Action 进行拦截并判断其 Action 类型,如果 Action 类型是函数,Redux Thunk 中间件会执行该函数,在函数中,返回一个新函数,新函数有 dispatch 和 getState 两个形参,可以在新函数内执行异步请求,异步请求完成后,再手动派发新的一个 Action 来更新 Redux Store 中的状态。
// 页面中dispatch一个Action
store.dispatch(getCinemaList(store.getState().cityReducer.cityId));
import axios from 'axios';
/**
* Action Creator内如果是同步返回一个普通js对象,{type:"xx",payload:xx}
* 如果是异步返回一个函数,需要redux-thunk中间件这个外挂支持:
*/
function getCinemaList(cityId) {
return (dispatch) => {
axios({
url: `https://m.maizuo.com/gateway?cityId=${cityId}&ticketFlag=1&k=4558896`,
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list',
},
}).then((res) => {
// console.log(res.data.data.cinemas);
dispatch({
type: 'fetch_cinemaList',
payload: res.data.data.cinemas,
});
});
};
}
export default getCinemaList;
- redux-promise:允许 Action Creator 返回一个 Promise 对象,把 Promise 的结果作为 payload 发送给 Reducer
- 工作原理:在 Redux 中,把 Redux Promise 中间件应用到 Redux Store 中,当在页面中 dispatch 一个 Action,Redux Promise 中间件会对 Action 进行拦截并判断其 Action 类型,如果 Action 的 payload 是一个 Promise 对象,Redux Promise 中间件会等待 Promise 对象解析,在 Action 中返回一个 Promise 对象去执行异步请求,请求完成后 Redux Promise 中间件会自动派发新的 Action 把 Promise 的结果作为 payload,通知 Redux Store。
// 页面中dispatch一个Action
store.dispatch(getCinemaList(store.getState().cityReducer.cityId));
import axios from 'axios';
/**
* redux-promise中间件风格:
* Promise三种状态:fulfilled,pending,reject
*/
function getCinemaList(cityId) {
return axios({
url: `https://m.maizuo.com/gateway?cityId=${cityId}&ticketFlag=1&k=4558896`,
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list',
},
}).then((res) => {
// console.log(res.data.data.cinemas);
return {
type: 'fetch_cinemaList',
payload: res.data.data.cinemas,
};
});
}
export default getCinemaList;
npm i react-redux -S
root.render(
<Provider store={store}>
<App />
</Provider>
);
import styles from '../css/search.module.css';
import React, { useState, useEffect, useMemo } from 'react';
import { CinemaItem } from './Cinemas';
import getCinemaList from '../redux/actionCreators/getCinemaList';
import { connect } from 'react-redux';
function Search(props) {
// 让请求在redux中发,保证view层只处理ui层,如果先进的是search页面
useEffect(() => {
if (!props.cinemaList.length) {
props.getCinemaList(props.cityId);
} else {
console.log('从 store 缓存中读取');
}
}, []);
return (
<div className={styles.root}>
{!myValue.length && (
<div className={styles.nearArea}>
<p>离你最近</p>
<ul>
{props.cinemaList.slice(0, 5).map((item) => {
return <li key={item.cinemaId}>{item.name}</li>;
})}
</ul>
</div>
)}
</div>
);
}
// 传给属性
const mapStateToProps = (state) => {
console.log(state, 'reducer的state');
return {
cinemaList: state.CinemaReducer.cinemaList,
cityId: state.cityReducer.cityId,
};
};
// 回调方法
const mapDispatchToProps = {
getCinemaList,
hideTabbar() {
return {
type: 'tabbar_hide',
};
},
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
/**
*
* 实现connect HOC源码
* HOC:接收一个低级组件,返回一个具有某种功能的高级组件
* HOC能做到:
* 1.劫持渲染:比如让低级组件整个组件的字体变成红色等...
* 2.代码复用
* 3.增删改props
*/
const qiuConnect = (cb, obj) => {
const state = cb();
return (MyComponent) => {
return (props) => {
return (
<div style={{ color: 'red' }}>
<MyComponent {...state} {...props} {...obj} />
</div>
);
};
};
};
// 应用自己封装的connect源码
import React from 'react';
function NotFound(props) {
console.log(props, 'props');
return <div>404 not found</div>;
}
export default qiuConnect(
() => {
return {
a: 1,
b: 2,
};
},
{
aa() {
return 'aa';
},
bb() {
return 'bb';
},
}
)(NotFound);
npm i redux-persist -S
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // defaults to localStorage for web
// 将处理不同业务的reducer拆分出来
import tabbarReducer from './reducers/tabbarReducer';
import cityReducer from './reducers/cityReducer';
import CinemaReducer from './reducers/cinemaReducer';
import ReduxThunk from 'redux-thunk';
import ReduxPromise from 'redux-promise';
const reducer = combineReducers({
// Define a top-level state field named `todos`, handled by `todosReducer`
tabbarReducer,
cityReducer,
CinemaReducer,
});
// redux持久化
const persistConfig = {
key: 'root',
storage,
whitelist: ['CinemaReducer'], // 只有白名单的才会持久化
};
const persistedReducer = persistReducer(persistConfig, reducer);
// 配置redux-devtools
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
/**
* 在redux中处理异步需要redux-thunk(返回函数)或redux-promise(返回Promise对象)中间件加持
* 同步即返回纯js对象
*/
const store = createStore(
persistedReducer,
composeEnhancers(applyMiddleware(ReduxThunk, ReduxPromise))
);
let persistor = persistStore(store);
export { store, persistor };
root.render(
// Provider 把store通过context上下文传递给App组件,是所有组件都能拿到store的值
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>
);
-
redux-saga
是一个管理 Redux 应用中异步操作的中间件, 基于 ES6 的 Generator 函数和 Effect 对象来实现非阻塞异步性代码。 -
工作原理:当一个页面或组件中的 action 被 dispatch 时,Redux Saga 会拦截并监听该 Action,并执行与该 Action 相关的 Saga,Saga 通过 yield 关键字和特定的 effect 函数来发起异步请求,并暂停执行,等待该请求响应成功,异步执行完成,Saga 继续执行,通过 put 发出新的 Action,通知 Redux Store 更新状态。
-
generator
函数在执行时可以暂停,后面又可以恢复执行,也称协程函数。使用:*test() 定义,函数内部使用 yield 表达式来暂停执行函数,通过 next()方法继续执行,同时可以将参数传递给 yield 表达式。
// MyApp.js 页面调用异步请求
<button
onClick={() => {
const list = store.getState().list;
if (!list.length) {
// dispatch如果是异步的,一定要让自己的saga去监管,不能让reducer监管
// 1. 在页面中dispatch一个异步action
store.dispatch({
type: 'get-list',
});
} else {
console.log('缓存读取', list);
}
}}
>
click
</button>
// store.js
import { applyMiddleware, createStore } from 'redux';
import reducer from './reducer';
import createSagaMiddleWare from 'redux-saga';
import watchSage from './saga';
const sagaMiddleware = createSagaMiddleWare();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(watchSage); // 2. 监听saga任务,执行与该action相关的Saga
export default store;
// saga.js
import { takeEvery, put, fork } from 'redux-saga/effects';
function* watchSaga() {
yield takeEvery('get-list', getList);
// yield takeEvery('get-list2', getList2); // 如果是监听多个的话
}
// 异步处理请求
function* getList() {
// 阻塞地 3. 通过yield关键字和call函数调用异步请求,等待结果响应
let res = yield call(getListAction);
// 非阻塞地 4. Saga继续执行,通过put函数发出新的action,通知store更新状态
yield put({
type: 'change-list',
payload: res,
});
}
// 真正调请求
function getListAction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(['111', '222', '333']);
}, 2000);
});
}
export default watchSage;
// reducer.js
const reducer = (
prevState = {
list: [],
},
action
) => {
const newState = { ...prevState };
switch (action.type) {
case 'change-list1':
newState.list = action.payload;
return newState;
default:
return prevState;
}
};
export default reducer;
1.在 react 中,做 setState 更新的时候要对原有的对象进行深复制,不能影响老的 state;在 redux 中,也要对 prevState 老的状态深复制一份,然后再修改,修改完了再返回一个新状态。
npm i immutable -S
// 1.引用复制(浅复制)
const aObj = {
name: 'aaaa',
};
const aObj2 = aObj;
aObj2.name = 'bbb';
console.log('aObj', aObj, aObj2); // 影响了原对象:都被修改为bbb了
// 2.浅意义的深复制(只对普通数据类型深复制,对引用数据类型浅复制)
const bObj = {
name: 'aaa',
arr: [1, 2, 3],
obj: {
age: 18,
nation: '中国',
},
};
const bObj2 = { ...bObj };
bObj2.name = 'bbb'; // 没有影响了原对象
bObj2.arr.splice(1, 1); // 影响了原对象:arr是引用数据类型
bObj2.obj.nation = '美国'; // 影响了原对象:obj是引用数据类型
console.log('bObj', bObj, bObj2);
// 3.JSON.parse(JSON.stringify)(对undefined失效,复制不了值为undefined的键)
const cObj = {
name: 'aaa',
arr: [1, 2, 3],
text: undefined,
};
const cObj2 = JSON.parse(JSON.stringify(cObj));
cObj2.name = 'bbb'; // 没有影响了原对象
cObj2.arr.splice(1, 1); // 没有影响了原对象
console.log('cObj2', cObj, cObj2);
// 深拷贝:递归一层一层 (性能太差,消耗性能)
// immutable:使用旧数据创建新数据时,能够保证旧数据可用且不变
属性层级 | 设置属性 | 获取属性 | 普通 js 对象 -> immutable 对象 | immutable 对象 -> 普通 js 对象 | 修改属性内为数组的数据 |
---|---|---|---|---|---|
一层 | set("name","xxx") | get("name") | |||
一层 | setIn(["locaiton","province"],"中国") | getIn(["locaiton","province"]) | |||
不论层级 | fromJS() | toJS() | updateIn(["favor"], value => value.splice(index, 1)) |
import React, { useState } from 'react';
import { fromJS } from 'immutable';
/**
* fromJS:把immutable对象转为普通js对象
* setIn:修改多层级键的值 setIn([],xx)
* getIn:获取多层级键的值 getIn([])
* updateIn:修改List数组的键 updateIn([],value => xxx)
*/
export default function App() {
const obj = {
name: 'aaa',
location: {
province: '广东省',
city: '广州市',
},
favor: ['睡觉', '玩游戏', '吃美食'],
};
const [data, setdata] = useState(fromJS(obj));
// console.log(data.toJS());
return (
<div>
<button
onClick={() => {
// setdata(data.set("name", "bbb"));
setdata(data.setIn(['name'], 'bbb'));
}}
>
修改姓名
</button>
<p>姓名:{data.get('name')}</p>
<button
onClick={() => {
setdata(
data
.setIn(['location', 'province'], '浙江省')
.setIn(['location', 'city'], '嘉兴市')
);
}}
>
修改籍贯
</button>
<p>
籍贯:{data.getIn(['location', 'province'])}-
{data.getIn(['location', 'city'])}
</p>
<div>
{data.getIn(['favor']).map((item, index) => (
<div>
<span key={item}>{item}</span>
<button
onClick={() => {
setdata(
data.updateIn(['favor'], (value) => value.splice(index, 1))
);
}}
>
删除
</button>
<button
onClick={() => {
setdata(data.setIn(['favor', index], '和男朋友去玩'));
}}
>
修改
</button>
</div>
))}
</div>
</div>
);
}
import { List } from 'immutable';
/* 对数组进行任何操作,不会改变原数组 */
const arr1 = List([1, 2, 3]);
const arr2 = arr1.push(4);
const arr3 = arr2.unshift(0);
const arr4 = arr3.concat([5, 6, 7]);
console.log(arr1.toJS(), arr2.toJS(), arr3.toJS(), arr4.toJS());
十五、mobx
npm i mobx@5 -S
npm i mobx-react@5 -S
import { Provider } from 'mobx-react';
root.render(
// Provider 把store通过context上下文传递给App组件,是所有组件都能拿到store的值\
<Provider store={store}>
<App />
</Provider>
);
import React, { Component } from 'react';
import IndexRouter from './router/IndexRouter';
import TabBar from './components/TabBar';
import { inject, observer } from 'mobx-react';
/* 类组件中监听store状态 */
@inject('store') // 注入store
@observer // 变成观察组件
class MyApp extends Component {
componentDidMount = () => {
console.log(this.props.store.isShowTabbar);
};
render() {
return (
<div className="myapp">
<IndexRouter>
{/* 插槽的写法 */}
{this.props.store.isShowTabbar && <TabBar />}
</IndexRouter>
</div>
);
}
}
export default MyApp;
import { observable, action, runInAction } from 'mobx';
import axios from 'axios';
class Store {
@observable isShowTabbar = true; // 变成可观察数据
@observable cinemaList = [];
@action async fetchCinemaList() {
const list = await axios({
url: `https://m.maizuo.com/gateway?cityId=440100&ticketFlag=1&k=4558896`,
headers: {
'X-Client-Info':
'{"a":"3000","ch":"1002","v":"5.2.1","e":"16789325361560653676412929","bc":"110100"}',
'X-Host': 'mall.film-ticket.cinema.list',
},
}).then((res) => {
// console.log(res.data.data.cinemas);
return res.data.data.cinemas;
});
// 处理异步
runInAction(() => {
this.cinemaList = list;
});
}
@action showTabbar() {
this.isShowTabbar = true;
}
@action hideTabbar() {
this.isShowTabbar = false;
}
}
const store = new Store();
export default store;
npm i @babel/core @babel/plugin-proposal-decorators @babel/preset-env
npm i customize-cra react-app-rewired
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
const path = require('path');
const { override, addDecoratorsLegacy } = require('customize-cra');
function resolve(dir) {
return path.join(__dirname, dir);
}
const customize = () => (config, env) => {
config.resolve.alias['@'] = resolve('src');
if (env === 'production') {
config.externals = {
react: 'React',
'react-dom': 'ReactDOM',
};
}
return config;
};
module.exports = override(addDecoratorsLegacy(), customize());
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject"
},
npm i styled-components -S
import styled from 'styled-components';
// 第一种写法
const ULStyled = styled.ul({
li: {
flex: 1,
// 不支持hover
},
display: 'flex',
position: 'fixed',
bottom: 0,
width: '100%',
height: '50px',
lineHeight: '50px',
textAlign: 'center',
listStyle: 'none',
});
// 第二种写法
const ULStyled = styled.ul`
display: flex;
position: fixed;
bottom: 0;
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
list-style: none;
li {
flex: 1;
&:hover {
background: red;
}
}
`;
<ULStyled>
{tabs.map((item) => (
<li key={item}>{item}</li>
))}
</ULStyled>;
const InputStyled = styled.input({
outline: 'none',
borderBottom: '1px solid red',
});
const DivStyled = styled.div`
width: 50px;
height: 50px;
background: ${(props) => props.bg ?? 'yellow'};
`;
{/* 原生标签属性自动透传 */}
<InputStyled type="password" placeholder='请输入密码' />
{/* 自定义组件透传属性,通过props回调接收 */}
<DivStyled bg="blue"></DivStyled>
<DivStyled></DivStyled>
const ChildStyled = styled(Child)`
width: 100px;
height: 100px;
background: yellow;
text-align: center;
line-height: 100px;
`;
export default function App() {
return (
<div>
App
<ChildStyled />
</div>
);
}
// 子组件记得要设置className接收
function Child(props) {
return <div className={props.className}>Child</div>;
}
const ButtonStyled1 = styled.button({
width: '100px',
height: '100px',
background: 'red',
});
const ButtonStyled2 = styled(ButtonStyled1)({
background: 'yellow',
});
const ButtonStyled3 = styled(ButtonStyled1)({
background: 'blue',
});
import styled, { keyframes } from 'styled-components';
const box = keyframes`
from {
transform: rotate(0deg)
}
to {
transform: rotate(360deg)
}
`;
const DivStyled = styled.div`
width: 100px;
height: 100px;
line-height: 100px;
background: pink;
text-align: center;
animation: ${box} 1s infinite 0s;
`;
export default function App() {
return <DivStyled>动画</DivStyled>;
}
// App.js
import React, { Component } from 'react';
import styles from './App.module.css';
import PortalDialog from './components/PortalDialog';
class App extends Component {
state = {
isShow: false,
};
render() {
return (
<div className={styles.root}>
<div className={styles.left}></div>
<div className={styles.right}>
<button
onClick={() => {
this.setState({
isShow: true,
});
}}
>
click
</button>
{this.state.isShow && (
<PortalDialog
close={() => {
this.setState({
isShow: false,
});
}}
>
<div>111</div>
<div>222</div>
<div>333</div>
</PortalDialog>
)}
</div>
</div>
);
}
}
export default App;
/* App.css */
* {
margin: 0;
padding: 0;
}
.root {
display: flex;
height: 100vh;
}
.left {
width: 200px;
background-color: aqua;
position: relative;
/* 拼爹时代:如果left的层级高于right,那么right里面的Dialog只会在right区域弹出。
即使把Dialog的层级弄得很高也没有用,Dialog再高也不会高于父级的层级。
解决办法:① 除非left的层级低于right;
② 把Dialog插在body内,与挂载的根节点同级
*/
z-index: 10;
}
.right {
flex: 1;
background-color: blueviolet;
position: relative;
z-index: 5;
}
// PortalDialog.js
import React from 'react';
import ReactDOM from 'react-dom';
export default function PortalDialog(props) {
return ReactDOM.createPortal(
<div
style={{
width: '100%',
height: '100vh',
background: 'rgba(0,0,0,0.7)',
position: 'fixed',
top: 0,
left: 0,
// 一定要写比父组件高的层级
zIndex: 9999,
color: '#fff',
}}
>
PortalDialog
{props.children}
<button onClick={props.close}>close</button>
</div>,
document.body
);
}
-
懒加载一: React.lazy + Suspense(React16.6 提供)
- React.lazy 动态导入组件,实现代码分割和按需加载,提高性能。将导入的组件作为 Suspense 的子组件渲染。Suspense 的 fallback 用于在动态加载组件时显示加载占位符。
- 优点:优化首屏加载时间
- 缺点:1.不支持 SSR(服务端渲染) 2.如果浏览器不支持 ECMA 方式 import 组件的方式,需要使用第三方 polyfill 库进行兼容处理
import React, { Suspense, useState } from 'react';
// React.lazy动态导入组件,实现代码分割和按需加载,提高性能
const NowPlaying = React.lazy(() => import('./components/NowPlaying'));
const ComingSoon = React.lazy(() => import('./components/ComingSoon'));
export default function App() {
const [type, settype] = useState(1);
return (
<div>
App
<button onClick={() => settype(1)}>正在热映</button>
<button onClick={() => settype(2)}>即将上映</button>
{/*
将导入的组件作为Suspense的子组件渲染
Suspense的fallback用于在动态加载组件时显示加载占位符
*/}
<Suspense fallback={<div>正在加载loading中</div>}>
{type === 1 ? <NowPlaying /> : <ComingSoon />}
</Suspense>
</div>
);
}
- 懒加载二: react-loadable
- React Loadable 是一个用于 React 应用程序中实现代码拆分的 HOC。可以让代码拆分成小块,并在渲染时按需加载,减少首屏加载时间,提高性能。
import React, { useState } from 'react';
import Loadable from 'react-loadable';
import Loading from './components/Loading';
const NowPlaying = Loadable({
loader: () => import('./components/NowPlaying'),
loading: () => <Loading />,
});
const ComingSoon = Loadable({
loader: () => import('./components/ComingSoon'),
loading: () => <Loading />,
});
export default function App() {
const [type, settype] = useState(1);
return (
<div>
App
<button onClick={() => settype(1)}>正在热映</button>
<button onClick={() => settype(2)}>即将上映</button>
{type === 1 ? <NowPlaying /> : <ComingSoon />}
</div>
);
}
-
SSR 服务端渲染:在前端写好 html 和 css,给后端,在后端设定一个路由返回首页信息,后端需要用到模板引擎,比如 nodejs 就是用 jade
-
图片优化:图片压缩(精灵图)、懒加载图片(只加载视窗内的图片,react-lazyload)、图片缓存(设置合理的缓存策略,把经常使用的图片被浏览器缓存,下次访问直接从浏览器读取)、CDN 加速
-
减少首页的复杂计算:使用 useMemo 缓存计算结果,使用 useCallback 缓存函数
- 函数组件用来获取子组件的实例。如果需要获取子组件的方法,使用 useImperativeHandle 把方法挂载在 ref 下
import React, { useState, useImperativeHandle } from 'react';
export default function Parent() {
const myRef = React.createRef();
return (
<div>
Parent
<button
onClick={() => {
console.log(myRef.current);
const myRefInstance = myRef.current; // 父组件通过 ref 来引用子组件的实例
console.log(myRefInstance);
myRefInstance.test();
myRefInstance.setvalue('');
}}
>
click
</button>
<Child ref={myRef} />
</div>
);
}
// 函数组件本身没有实例,因此无法直接使用 ref,需要使用 forwardRef 向子组件传递 ref
const Child = React.forwardRef((props, ref) => {
const [value, setvalue] = useState('111');
const test = () => {
console.log('test');
};
// 把方法和state值绑定在ref上,让父组件调用
useImperativeHandle(ref, () => ({
test,
setvalue,
}));
return (
<div>
Child
<input
ref={ref}
value={value}
onChange={(evt) => setvalue(evt.target.value)}
/>
</div>
);
});
- 类组件:PureComponent 和 shouldComponentUpdate
import React, { Component, useState, PureComponent } from 'react';
export default function App() {
const [name, setname] = useState('xiaoming');
const [age] = useState(19);
const [obj, setobj] = useState({
name: 'xiaolan',
age: 18,
});
return (
<div>
{name}
<button
onClick={() => {
setname(Math.random(1));
}}
>
改变name
</button>
{/* name和age有一个变了,Child才会更新;如果两个都没变,Child组件直接复用上次的 */}
<Child1 name={name} age={age} />
<button
onClick={() => {
setobj({
name: 'yellow',
age: 20,
});
}}
>
改变obj
</button>
<Child2 obj={obj} />
</div>
);
}
// 类组件:PureComponent浅比较(this.props是基本类型的)
class Child1 extends PureComponent {
componentDidUpdate(prevProps, prevState) {
console.log('Child1', 111);
}
render() {
return <div>Child2</div>;
}
}
// 类组件:深比较(this.props是引用类型的)
class Child2 extends Component {
shouldComponentUpdate = (nextProps, nextState) => {
// console.log(this.props, nextProps);//this.props上一次的props;nextProps下一次的props
if (JSON.stringify(this.props) !== JSON.stringify(nextProps)) {
return true;
}
return false;
};
componentDidUpdate(prevProps, prevState) {
console.log('update');
}
render() {
return <div>Child2</div>;
}
}
- 函数组件:React.memo 和 React.memo + lodash/isEqual
import React, { useState } from 'react';
import deepEqual from 'lodash/isEqual';
export default function App() {
const [name, setname] = useState('xiaoming');
const [age] = useState(19);
const [obj, setobj] = useState({
name: 'xiaolan',
age: 18,
});
return (
<div>
{name}
<button
onClick={() => {
setname(Math.random(1));
}}
>
改变name
</button>
<Child name={name} age={age} />
<button
onClick={() => {
setobj({
name: 'yellow',
age: 20,
});
}}
>
改变obj
</button>
<Child2 obj={obj} />
</div>
);
}
// 函数组件:memo浅比较(props是基本属性)
const Child = React.memo(() => {
console.log('child1', 1111);
useEffect(() => {
console.log('useEffect');
}, []);
return <div>Child1</div>;
});
// 函数组件:isEqual 函数进行深比较判断props属性是否相等
const Child2 = React.memo(() => {
console.log('Child2', 2222);
useEffect(() => {
console.log('useEffect');
}, []);
return <div>Child2</div>;
}, deepEqual);
-
useCallback:缓存回调函数,在依赖项改变时才重新创建新的函数实例
-
useMemo:缓存计算结果,在依赖项改变时才重新渲染计算
-
优化网络请求:缓存技术、延迟加载、分页加载等方式减少对后端的请求次数
-
使用虚拟化技术:对长列表或大型数据集,先渲染可见区域的内容,减少 DOM 的数量