React
特点:
- 采用组件化的模式、声明式编码,提高开发效率及组件复用率
- 使用虚拟rom + 优秀的Diffing算法,尽量减少与真实DOM的交互
格式:
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script type="text/javascript" src="./js/react.development.js"></script>
<script type="text/javascript" src="./js/react-dom.development.js"></script>
<script type="text/javascript" src="./js/babel.min.js"></script>
<script type="text/babel">
</script>
</head>
<body>
<div id="test">
</div>
</body>
</html>
Hello world
<div id="test">
</div>
<script type="text/babel">
const vdom = <h1>hello</h1>
ReactDOM.render(vdom, document.getElementById('test'))
</script>
1.1.1. JSX
全称: JavaScript XML
react
定义的一种类似于XML
的JS
扩展语法,规则
- 定义虚拟dom时不能有引号
- 标签中混入js表达式时使用
{}
- 样式的类名使用
className
- 内联样式名称小驼峰
- 不能有多个根标签
- 标签必须闭合
- 标签首字母小写开头,则将会转为同名元素,如果
html
中没有同名标签将会保存。如果开头是大写字母,React将会渲染对应的组件
<script type="text/babel">
const myId = 'AbCd';
const data = "这是内容"
const style = {
border: '10px solid black',
borderRadius: 10 + 'px'
}
const vdom = (
<div className="box" style={style}>
<span style={{color: 'white', 'fontSize': 30 + 'px'}}>文字</span>
</div>
);
ReactDOM.render(vdom, document.getElementById('test'))
</script>
<style>
.box {
width: 100px;
height: 100px;
background-color: red;
}
</style>
数组自动遍历
react自动遍历数组,如果想要在页面上显示需要将数组做一个映射:
<script type="text/babel">
const data = ["你好", "世界", "解决"]
const vdom = (
<div>
<h1>56555</h1>
<ul>
{data.map((it, i) => <li key={i}>{it}</li>)}
</ul>
</div>
)
ReactDOM.render(vdom, document.getElementById("test"))
</script>
函数组件
<script type="text/babel">
const Com = () => {
return <h1>66666</h1>
}
ReactDOM.render(<Com/>, document.getElementById("test"))
</script>
组件名必须首字母大写
类组件
<script type="text/babel">
class MyCom extends React.Component {
render() {
return (
<div>
<h1>6666</h1>
</div>
)
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
</script>
有状态的组件称为复杂组件,无状态的组件成为简单组件
组件实例的三大核心:
state
props
refs
state
可以保存状态:
<script type="text/babel">
class MyCom extends React.Component {
constructor(props) {
super(props);
this.state = {flag: false}
}
render() {
const {flag} = this.state;
return (
<div>
<h1>你好,{flag ? '世界' : '天空'}</h1>
</div>
);
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
</script>
事件
原生的三种绑定事件的方式:
<script type="text/javascript">
window.onload = () => {
const btn1 = document.getElementById("btn1");
btn1.addEventListener('click', () => alert("111"))
const btn2 = document.getElementById("btn2");
btn2.onclick = () => alert("222")
}
let b3 = () => alert("333")
</script>
<button id="btn1">11111</button>
<button id="btn2">22222</button>
<button onclick="b3()">33333</button>
React
中推荐使用第3种
setState
分为对象式和函数式
对象式
setState
用来更新页面上的数据,构造器只调用一次,render
调用多次,每次调用setState
都会调用一次render
,是异步更新的,还可以指定一个更新后的回调,回调是可选的
plus = () => {
let {count} = this.state;
count++;
this.setState({count}, () => {
console.log(this.state)
})
}
类中定义的方法都开启了严格模式,也就是此时哪个对象调用方法,哪个对象就是方法中的this
的指向,可以通过bind(Object)
函数返回一个更改this
指向后的新函数
<script type="text/babel">
class MyCom extends React.Component {
constructor(props) {
super(props);
this.state = {
flag: false,
content: "内容"
}
this.click = this.click.bind(this);
}
render() {
const {flag, content} = this.state;
return (
<div>
<h1 onClick={this.click}>你好,{flag ? '世界' : '天空'}--{content}</h1>
</div>
);
}
click() {
const {flag} = this.state;
// 状态不能直接更改
// this.state.flag = !this.state.flag;
console.log(this);
// 状态必须通过setState更改,是合并,而不是替换
this.setState({flag: !flag})
console.log(this.state);
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
</script>
精简代码:
可以通过以下方式在一个类上追加一个属性,效果等同于在构造器中使用this.xxx = xxx
class Car {
a = 1
}
可以把方法写成箭头函数的样子,因为箭头函数没有自己的this
,箭头函数的this
取决于它的外部,类中的箭头函数中的this
就是这个类的实例对象
所以精简形式为:
<script type="text/babel">
class MyCom extends React.Component {
state = {
flag: true,
content: "天"
}
render() {
const {flag, content} = this.state;
return (
<div>
<h1 onClick={this.click}>你好,{flag ? '世界' : '天空'}--{content}</h1>
</div>
);
}
click = () => {
const {flag} = this.state
this.setState({flag: !flag})
console.log(this.state);
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
</script>
函数式
this.setState(onchange, [callback])
:
onchange
可以接收两个参数,第一个参数为state
,第二个参数为props
,它的返回值将作为state
中设置的值callback
是可选的
plus = () => {
this.setState((state, props) => {
return {
count: state.count + 1
}
}, () => {
console.log(this.state)
})
}
props
基本的用法, 直接在组件身上写属性即可:
<script type="text/babel">
const obj = {
name: "aaaa",
age: 12,
size: 100
}
class MyCom extends React.Component {
state = {
count: 1
}
render() {
const {name} = this.props;
console.log(this.props);
console.log(name)
return (
<div>
<ul>
<li>姓名:{name.name}</li>
<li>年龄:{name.age}</li>
<li>大小:{name.size}</li>
</ul>
</div>
);
}
}
ReactDOM.render(<MyCom good="你好" test="测试" name={obj}/>, document.getElementById("test"))
</script>
...
运算符可以用来展开数组、合并数组、可变参数、对象的深拷贝:
const arr = ["aa", "bb", "cc"]
console.log(...arr)
// -----------------------------
const arr = ["aa", "bb", "cc"]
const arr2 = ["11", "22", "33"]
const b = [...arr, ...arr2]
console.log(b)
// -----------------------------
const fun = (...a) => {
console.log(a);
}
fun(1, 2, 3, 5, 6)
// -----------------------------
const p = {
id: "6666",
age: 7777
}
const p2 = {...p}
console.log(p2)
console.log({...p})
// 还可以在深拷贝对象时修改、合并属性
const ppp = {...p, id: 5}
原生JavaScript
不允许直接使用...
展开一个对象,即:...obj
是错误的,但React + Babel
可以,但仅仅可以在标签上使用,其他地方不能够随意使用:
ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
此时的obj
中的所有属性将会直接放到props
上
对props进行限制
可以对类型显示,需要引入prop-types.js
<script type="text/babel">
const obj = {
name: "aaaa",
age: 12,
fun() {
return 1
}
}
class MyCom extends React.Component {
state = {
count: 1
}
render() {
const {name, age, size} = this.props;
return (
<div>
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>大小:{size}</li>
</ul>
</div>
);
}
}
MyCom.propTypes = {
// 这个属性必须要有
name: PropTypes.string.isRequired,
age: PropTypes.number,
// 这个属性是函数,因为function是关键字,为了避免冲突
fun: PropTypes.func
}
MyCom.defaultProps = {
size: 10000
}
ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
</script>
props
是只读的
在以上代码中可以看出propTypes
和defaultProps
是给这个类加的属性,所以可以将这两个属性移动到类中,并用static
修饰
<script type="text/babel">
const obj = {
name: "aaaa",
age: 12,
fun() {
return 1
}
}
class MyCom extends React.Component {
state = {
count: 1
}
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
fun: PropTypes.func
}
static defaultProps = {
size: 10000
}
render() {
const {name, age, size} = this.props;
return (
<div>
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>大小:{size}</li>
</ul>
</div>
);
}
}
ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
</script>
函数组件使用props
函数组件仅能用props
:
<script type="text/babel">
const obj = {
name: "名称",
age: 12,
sex: "男"
}
let MyCom = (props) => {
const {name, age, sex} = props;
return (
<div>
<ul>
<li>名称:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
</div>
)
}
MyCom.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string
}
MyCom.defaultProps = {
age: 1000
}
ReactDOM.render(<MyCom {...obj}/>, document.getElementById("test"))
</script>
refs
将实际的dom
元素挂载到this.refs
对象上
字符串ref
ref
指定的名称为字符串,这种方式不推荐,存在着效率问题,可能会弃用
class MyCom extends React.Component {
content = '';
getContent = () => {
const {input1} = this.refs;
alert(input1.value)
}
getContent2 = () => {
const {input2} = this.refs;
alert(input2.value);
}
render() {
return (
<div>
<input ref="input1" type="text"/>
<br/>
<br/>
<button onClick={this.getContent}>提交</button>
<br/>
<br/>
<input ref="input2" onBlur={this.getContent2} type="text"/>
</div>
)
}
}
ReactDOM.render(<MyCom />, document.getElementById("test"))
回调函数形式ref
可以直接挂载到实例身上:
class MyCom extends React.Component {
content = '';
getContent = () => {
const {input1} = this;
alert(input1.value)
}
getContent2 = () => {
const {inp} = this;
alert(inp.value);
}
render() {
return (
<div>
<input ref={c => this.input1 = c} type="text"/>
<br/>
<br/>
<button onClick={this.getContent}>提交</button>
<br/>
<br/>
<input ref={c => this.inp = c} onBlur={this.getContent2} type="text"/>
</div>
)
}
}
ReactDOM.render(<MyCom />, document.getElementById("test"))
React.createRef()方法创建ref
每个通过React.createRef()
创建的ref
容器只能为一个标签绑定,多次绑定将会替换
class MyCom extends React.Component {
myInputRef = React.createRef();
state = {
flag: false
}
getValue = () => {
alert(this.myInputRef.current.value)
}
render() {
return (
<div>
<input ref={this.myInputRef} type="text"/>
<button onClick={this.getValue}>获取值</button>
</div>
)
}
}
ReactDOM.render(<MyCom />, document.getElementById("test"))
事件
- 通过
onXxx
属性指定事件处理函数(注意大小写) React
使用的是自定义(合成)事件, 而不是使用的原生DOM
事件React
中的事件是通过事件委托方式处理的(委托给组件最外层的元素)- 通过
event.target
得到发生事件的DOM
元素对象
不要过度的使用ref
,当发生事件的元素就是为了进行操作的元素可以省略ref
,从而直接通过事件中的target
获取到这个元素中的dom
:
class MyCom extends React.Component {
myInputRef = React.createRef();
state = {
flag: false
}
getValue = (e) => {
// alert(this.myInputRef.current.value)
console.log(e.target);
}
render() {
return (
<div>
<input ref={this.myInputRef} type="text"/>
<button onClick={this.getValue}>获取值</button>
</div>
)
}
}
ReactDOM.render(<MyCom />, document.getElementById("test"))
非受控组件
组件中的数据是现用现取,之前写的组件都属于非受控组件,例如:
class MyCom extends React.Component {
login = (e) => {
console.log(this);
alert("username = " + this.username.value + "\npassword = " + this.password.value);
}
render() {
return (
<div>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
<br/>
密码:<input ref={c => this.password = c} type="text" name="username"/>
<br/>
<button onClick={this.login}>登录</button>
</div>
)
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
受控组件
随着内容的输入,将会把内容更改到state
中,等需要时,再从状态中取出,这种组件称为受控组件,使用受控组件可以减少ref
的使用,React
建议不要过度使用ref
以下组件就是受控组件:
class MyCom extends React.Component {
state = {
username: '',
password: ''
}
login = (e) => {
const {username, password} = this.state;
alert(`username: ${username}\npassword: ${password}`)
}
changeUsername = (e) => {
this.setState({username: e.target.value})
}
changePassword = (e) => {
this.setState({password: e.target.value})
}
render() {
const {username, password} = this.state;
return (
<div>
用户名:<input onChange={this.changeUsername}/>
<br/>
密码:<input onChange={this.changePassword} type="password"/>
<br/>
<button onClick={this.login}>登录</button>
<br/>
你输入的信息:<br/>
<p>username:{username}</p>
<p>password:{password}</p>
</div>
)
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
高阶函数
如果一个函数a
符合以下条件中的任意一个,那么该函数为高阶函数:
- 接受的参数为函数
- 返回值为函数
函数的柯里化:通过函数调用继续返回函数的方式
可以发现之前的例子中需要为每个表单项都要提供一个事件改变的函数,可以使用函数柯里化统一管理:
class MyCom extends React.Component {
state = {
username: '',
password: ''
}
login = (e) => {
const {username, password} = this.state;
alert(`username: ${username}\npassword: ${password}`)
}
dataChange = (name) => {
return (e) => {
this.setState({
[name]: e.target.value
})
}
}
render() {
const {username, password} = this.state;
return (
<div>
用户名:<input onChange={this.dataChange('username')}/>
<br/>
密码:<input onChange={this.dataChange('password')} type="password"/>
<br/>
<button onClick={this.login}>登录</button>
<br/>
你输入的信息:<br/>
<p>username:{username}</p>
<p>password:{password}</p>
</div>
)
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
生命周期(16)
组件挂载时的生命周期:
构造器
先执行componentWillMount()
,组件挂载前render()
,组件渲染componentDidMount()
会在组件挂载完毕后调用,由于这个函数是由对象实例调用的,所以this
的指向就是实例,无需写成箭头函数的形式componentWillUnmount()
组件卸载前调用
class MyCom extends React.Component {
state = {
opacity: 1
}
remove = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
componentDidMount() {
this.start = setInterval(() => {
let {opacity} = this.state;
opacity -= 0.1;
if (opacity <= 0) {
opacity = 1;
}
this.setState({
opacity
})
}, 200);
}
componentWillUnmount() {
clearInterval(this.start)
}
render() {
const {opacity} = this.state;
return (
<div>
<p style={{
opacity
}}>123456789</p>
<button onClick={this.remove}>消失</button>
</div>
);
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
调用setState()
方法,组件更新时的生命周期:
shouldComponentUpdate()
组件是否要更新,返回值为布尔值,如果为false
,以下回调将不会执行,页面也不会渲染componentWillUpdate()
组件将要更新了render()
渲染页面componentDidUpdate()
组件更新后
class MyCom extends React.Component {
constructor(props) {
super(props);
console.log("m-构造器执行..")
}
remove = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
componentWillMount() {
console.log("m-组件将要挂载...")
}
componentDidMount() {
console.log("m-组件挂载....")
}
componentWillUnmount() {
console.log("m-组件将要卸载...")
}
shouldComponentUpdate() {
console.log("m-组件是否需要更新...")
return true;
}
componentWillUpdate() {
console.log("m-组件将要更新了...")
}
componentDidUpdate() {
console.log("m-组件更新了...")
}
state = {
count: 0
}
add = () => {
this.setState({count: this.state.count + 1})
}
render() {
console.log("m-render执行...")
const {count} = this.state;
return (
<div>
<h1>{count}</h1>
<button onClick={this.add}>+1</button>
<br/>
<br/>
<button onClick={this.remove}>卸载</button>
</div>
);
}
}
ReactDOM.render(<MyCom/>, document.getElementById("test"))
forceUpdate()
force中文为强迫、强制、力量
强制更新可以在数据不进行改变时进行更新,一系列的回调:
componentWillUpdate()
组件将要更新了render()
渲染页面componentDidUpdate()
组件更新后
也就是不经过shouldComponentUpdate()
的控制
class MyCom extends React.Component {
constructor(props) {
super(props);
console.log("m-构造器执行..")
}
remove = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
componentWillMount() {
console.log("m-组件将要挂载...")
}
componentDidMount() {
console.log("m-组件挂载....")
}
componentWillUnmount() {
console.log("m-组件将要卸载...")
}
shouldComponentUpdate() {
console.log("m-组件是否需要更新...")
return true;
}
componentWillUpdate() {
console.log("m-组件将要更新了...")
}
componentDidUpdate() {
console.log("m-组件更新了...")
}
update = () => {
console.log("m-点击了强制更新按钮...");
this.state.count += 10;
this.forceUpdate();
}
state = {
count: 0
}
add = () => {
this.setState({count: this.state.count + 1})
}
render() {
console.log("m-render执行...")
const {count} = this.state;
return (
<div>
<h1>{count}</h1>
<button onClick={this.add}>+1</button>
<br/>
<br/>
<button onClick={this.remove}>卸载</button>
<button onClick={this.update}>强制更新</button>
</div>
);
}
}
父子组件的更新时的回调:
- 父组件
shouldComponentUpdate()
- 父组件
componentWillUpdate()
- 父组件
render()
- 子组件
componentWillReceiveProps()
(如果有传的Props
) - 子组件
shouldComponentUpdate()
- 子组件
componentWillUpdate()
- 子组件
render()
- 子组件
componentDidUpdate()
- 父组件
componentDidUpdate()
componentDidUpdate()
可以带有两个参数,分别是之前的props
和之前的state
componentDidUpdate(preProps, PreState) {
console.log("son-组件更新了", preProps, PreState)
}
class Father extends React.Component {
state = {
content: "你好"
}
index = 0;
arr = ['你好', '我好', '大家好']
updateFather = () => {
this.index++;
this.index %= this.arr.length;
this.setState({content: this.arr[this.index]})
}
render() {
console.log("Father-渲染页面")
const {content} = this.state;
return (
<div>
<p>父组件中此时的内容:{content}</p>
<br/>
<button onClick={this.updateFather}>修改父组件</button>
<br/>
<br/>
<Son content={content}/>
</div>
);
}
shouldComponentUpdate() {
console.log("Father-组件是否应该更新")
return true;
}
componentWillUpdate() {
console.log("Father-组件准备更新")
}
componentDidUpdate() {
console.log("Father-组件更新了")
}
}
class Son extends React.Component {
render() {
console.log("son-渲染页面")
return (
<div>
<p>子组件此时的内容为:{this.props.content}</p>
</div>
);
}
componentWillReceiveProps() {
console.log("son-接收到Props了")
}
shouldComponentUpdate() {
console.log("son-组件是否应该更新")
return true;
}
componentWillUpdate() {
console.log("son-组件准备更新")
}
componentDidUpdate() {
console.log("son-组件更新了")
}
}
ReactDOM.render(<Father/>, document.getElementById("test"))
常用的有3个:
componentDidMount()
,一般做初始化,例如开始定时器、发送网络请求、订阅消息render()
,必须要用的componentWillUnmount()
,一般做一些收尾的事,例如:关闭定时器、取消消息订阅
生命周期(17)
所有带有will
单词的回调方法都推荐添加UNSAFE_
前缀,并且不再推荐使用了
derived
中文为衍生,dɪˈraɪvd
getDerivedStateFromProps
,这个方法必须为静态的,返回值要么是一个状态对象(可以认为一个state
对象),要么是null
,当为一个状态对象时,无论再怎么调用setState()
将无法继续修改值,即使调用forceUpdate()
也无法强制更新,可以接收到一个props
对象和一个state
对象,如果state
对象没有被赋值,控制台会看到错误,每当数据更新时,这个方法中的state
为旧值
应用场景:state
的值在任何时候都取决于props
static getDerivedStateFromProps(props) {
console.log("father-getDerivedStateFromProps");
return {
content: "儿子"
}
}
static getDerivedStateFromProps(props, state) {
console.log("props = ", props);
console.log("state = ", state);
return props;
}
getSnapshotBeforeUpdate()
,返回值为null
或者一个值,返回的值会被componentDidUpdate()
的第三个参数接收:
getSnapshotBeforeUpdate(preProps, preState) {
console.log("Father-getSnapshotBeforeUpdate", preProps, preState);
return "你好,我是getSnapshotBeforeUpdate()传递的值";
}
componentDidUpdate(preProps, preState, snapshot) {
console.log("Father-组件更新了", preProps, preState, snapshot);
}
应用场景:
每隔一定的时间追加一定的内容,而原有的内容不随着滚动
class News extends React.Component {
state = {
arr: []
}
componentDidMount() {
setInterval(() => {
const {arr} = this.state;
const content = `内容${arr.length + 1}`
this.setState({
arr: [content, ...arr]
})
}, 1000)
}
getSnapshotBeforeUpdate() {
return this.box.scrollHeight;
}
componentDidUpdate(preProps, preState, snapshot) {
this.box.scrollTop += this.box.scrollHeight - snapshot;
}
render() {
const {arr} = this.state;
return (
<div ref={(t) => this.box = t} className="box">
{arr.map((it, index) => <div key={index} className="item">{it}</div>)}
</div>
);
}
}
ReactDOM.render(<News/>, document.getElementById("test"))
Diffing算法
如果有值通过setState
进行改变,他只会将改变的那个标签进行更新
key
的用法:
- 在数据改变时,如果新的虚拟
dom
和旧的虚拟dom有相同的key
,如果有将会对比两者的内容- 如果内容改变了,用新的的虚拟
dom
替换真实dom
- 如果没有改变,直接用之前旧的真实
dom
- 如果内容改变了,用新的的虚拟
- 如果没有相同的
key
,将会根据数据创建新的dom
渲染到页面
脚手架
生成的index.html
文件中头部信息内容含义:
<!-- %PUBLIC_URL%代表public这个文件夹-->
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- 用来配置浏览器页签和地址栏的颜色,仅支持Android系统-->
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<!-- 将网站图标添加到桌面时的图标-->
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- pwa使用-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
index.js
中的:
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
作用是检查React
子组件中用法是否合理,是否用过不合理的api
以下用于记录页面的性能:
import reportWebVitals from './reportWebVitals';
reportWebVitals();
最基本的结构:
import React from "react";
class App extends React.Component {
render() {
return (
<div>
hello
</div>
);
}
}
export default App;
import {Component} from "react"; // 采用的分别暴露方式引入的
class App extends Component {
render() {
return (
<div>
hello666
</div>
);
}
}
export default App;
继续优化:
import {Component} from "react";
export default class App extends Component {
render() {
return (
<div>
hello666
</div>
);
}
}
CSS模块化
css
文件命名时按照文件名.module.css
使用:
import {Component} from "react";
import hello from "../css/my.module.css"
export default class Hello extends Component {
render() {
return (
<div id={hello.my}>
你好
</div>
);
}
}
代码片段
rsf
函数组件快速生成,rcc
快速生成类式组件,sst
生成this.setState();
propTypes
对传递的类型限制,需要安装:
npm install prop-types
引入:
import PropTypes from "prop-types";
消息的订阅和发布
解决兄弟组件之间的通信,使用pubsubjs
npm install pubsub-js
接收消息的一方在生命周期componentDidMounted
回调中指定:
PubSub.unsubscribe('MY TOPIC');
PubSub.subscribe('MY TOPIC', (name, data) => {});
发送消息的一方使用:PubSub.publish('MY TOPIC', {消息});
发送消息
Fetch
中文拿、取,Fetch
是使用Promise
开发的,基本用法:
fetch('https://up.api.daidr.me/apis/hitokoto.json')
.then(res => res.json())
.then(data => console.log(data))
async-await
:
const response = await fetch('https://up.api.daidr.me/apis/hitokoto.json');
const result = await response.json();
console.log(result);
路由
使用的React-router-dom
库,是官方维护的路由库
npm install react-router-dom
- 需要使用
<Link to="路径" />
标签代替<a />
标签 <Link/>
标签需要放到Router
标签中,Router
标签有多个- 可以使用
<BrowserRouter></BrowserRuter>
标签包裹起来 - 也可以使用
<HashRouter></HashRouter>
- 可以使用
- 使用
<Route path="路径" component={组件} />
指定某个路径所对应的组件 Route
也需要放到一个Router
标签下- 如果
Route
和Link
标签不在同一个Router
标签下时,当点击了Link
标签时,不会引起页面内容变化,只能刷新页面才能够看到变化 - 可以将
App
包裹在一个Router
下,这样可以实现所有的Route
和Link
都在同一个Router
标签下了 - 只需要在
index.js
中进行包裹即可
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import {BrowserRouter} from "react-router-dom";
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App/>
</BrowserRouter>
);
app.js
import {Component} from "react";
import {Link, Route} from "react-router-dom";
import Home from "./component/Home";
import About from "./component/About";
import './App.css';
export default class App extends Component {
render() {
return (
<div>
<Link to={'/home'}>home</Link>
<br/>
<Link to={'/about'}>about</Link>
<div className="content">
<Route path={'/home'} component={Home}/>
<Route path={'/about'} component={About}/>
</div>
</div>
);
}
}
路由组件:<Route path={'/home'} component={Home}/>
一般组件:<Home></Home>
路由组件
路由组件中的props
中会带有以下信息:
{
"history": {
"length": 7,
"action": "POP",
"location": {
"pathname": "/home/a",
"search": "",
"hash": ""
}
},
"location": {
"pathname": "/home/a",
"search": "",
"hash": ""
},
"match": {
"path": "/home",
"url": "/home",
"isExact": false,
"params": {}
}
}
NavLink
作用效果和<Link/>
相同,Link
标签将会被转换到<a>
标签
<NavLink to={'/home'}>home</NavLink>
<br/>
<NavLink to={'/about'}>about</NavLink>
如果当前路由匹配这个NavLink
中to
属性写的路径,那么默认情况下浏览器中的这个标签将会追加aria-current="page" class="active"
属性
可以添加activeClassName={'名称'}
属性实现自定义路由匹配时所添加的class
this.props.children
使用这种方式嵌入的组件可以在this.props.children
中获取到
<Father>
<Son />
</Father>
import React, {Component} from 'react';
class Father extends Component {
render() {
console.log(this)
const {children} = this.props;
return (
<div>
父亲
<div>
{children}
</div>
</div>
);
}
}
export default Father;
Switch组件
如果一个路径中对应了多个组件,那么两个组件都显示
<Route path={'/home'} component={Home}/>
<Route path={'/about'} component={About}/>
<Route path={'/about'} component={Test} />
这就代表着即使从上到下扫描后匹配到了组件,仍会继续向下扫描继续匹配,如果组件特别多将会存在效率上的问题,使用switch
组件解决此问题
<Switch>
<Route path={'/home'} component={Home}/>
<Route path={'/about'} component={About}/>
<Route path={'/about'} component={Test} />
</Switch>
匹配
默认情况下是模糊匹配,<Route path={'/home'} component={Home}/>
中,/home
、/home/aa/n
等都能匹配上
添加exact
属性实现精准匹配,iɡˈzakt
,中文为准确的,<Route exact={true} path={'/about'} component={About}/>
或者<Route exact path={'/about'} component={About}/>
重定向
可以设置某个路径下的默认的一个路由
<Switch>
<Route path={'/home'} component={Home}/>
<Route path={'/about'} component={About}/>
<Redirect to={'/home'} />
</Switch>
<Redirect/>
进行兜底,卸载最下边
接收参数
可以采用:名称
作为占位符,子组件中可以通过this.props.match.params
获取占位符中的参数
class Home extends Component {
state = {
arr: [
{
id: 1,
title: "标题",
content: "这是内容"
},
{
id: 2,
title: "标题222",
content: "这是内容2222"
},
{
id: 3,
title: "标题333",
content: "这是内容333"
},
]
}
render() {
const {arr} = this.state;
return (
<div className={"my-home"}>
<ul>
{
arr.map(it =>
<li key={it.id}>
<Link to={`/home/message/${it.id}`}>消息{it.id}</Link>
</li>)
}
</ul>
<div
style={{
border: "1px solid black",
margin: "auto",
width: '250px',
height: '200px',
color: 'black'
}}>
<p>消息区</p>
<Route path={'/home/message/:id'} component={Message} />
</div>
</div>
);
}
}
export default Home;
class Message extends Component {
componentDidMount() {
console.log(this.props.match.params);
}
render() {
const {id, title, content} = this.props.match.params;
return (
<div>
<p>id:{id}</p>
<p>title:{title}</p>
<p>content:{content}</p>
</div>
);
}
}
export default Message;
search参数
也就是query参数,可以使用第三方库解析
npm install query-string
import qs from "query-string";
console.log(qs.parse(this.props.location.search));
<div className={"my-home"}>
<ul>
{
arr.map(it =>
<li key={it.id}>
<Link to={`/home/message/?id=${it.id}&title=${it.title}&content=${it.content}`}>消息{it.id}</Link>
</li>)
}
</ul>
<div
style={{
border: "1px solid black",
margin: "auto",
width: '250px',
height: '200px',
color: 'black'
}}>
<p>消息区</p>
<Route path={'/home/message'} component={Message} />
</div>
</div>
node.js
自带了qs
模块,可以直接使用这个模块:
import React, {Component} from 'react';
import qs from "qs";
class Message extends Component {
componentDidMount() {
// 开头会包含?,所以要取子串
console.log(qs.parse(this.props.location.search.substring(1)));
}
render() {
const {id, title, content} = this.props.match.params;
return (
<div>
<p>id:{id}</p>
<p>title:{title}</p>
<p>content:{content}</p>
</div>
);
}
}
export default Message;
render() {
const {id, title, content} = qs.parse(this.props.location.search.substring(1))
return (
<div>
<p>id:{id}</p>
<p>title:{title}</p>
<p>content:{content}</p>
</div>
);
向路由传递state参数
如果使用query
、param
的方式进行传递时,传递的参数将会暴露在地址栏中
<li key={it.id}>
<Link to={{
pathname: '/home/message',
state: it
}}>消息{it.id}</Link>
</li>)
接收:
const {id, title, content} = this.props.location.state;
如果使用的是<BrowserRouter>
时,刷新页面时内容不丢失,如果清空缓存将会丢失,为了避免这种情况,可以将写法变为:
const {id, title, content} = this.props.location.state || {};
replace
默认情况下使用的是push
方式,可以切换为replace
方式,一个保留历史记录,一个不保留
<NavLink replace activeClassName={'aaa'} to={'/home'}>home</NavLink>
<br/>
<NavLink replace activeClassName={'aaa'} to={'/about'}>about</NavLink>
方法调用方式跳转路由
可以通过调用方法进行跳转,query
和param
可以通过拼接字符串实现:
this.props.history.push('/about')
this.props.history.replace('/about')
参数2可以填写state
:
this.props.history.push('/home/message', {
id: '8888',
title: '565656566',
content: '9999999'
})
this.props.history.replace('/home/message', {
id: '8888',
title: '565656566',
content: '9999999'
})
<button onClick={() => this.props.history.goBack()}>后退</button>
<button onClick={() => this.props.history.goForward()}>前进</button>
withRouter
只有路由组件才能使用路由中的 this.props.xxx
方法和属性
路由组件:<Route path={'/home'} component={Home}/>
一般组件:<Home></Home>
为了解决此问题,引入了withRouter
,返回值是一个新组件
import {withRouter} from "react-router-dom";
class App extends Component {
render() {
return (
<div>
<button onClick={() => this.props.history.goBack()}>后退</button>
<button onClick={() => this.props.history.goForward()}>前进</button>
</div>
);
}
}
export default withRouter(App);
Redux
是一个专门做状态管理的JS
库 ,集中管理应用中共享的状态,所有共享的数据都可以交给Redux
管理,需要用到的情况:
- 某个组件的状态,需要让其他组件可以随时拿到(共享)
- 一个组件需要改变另一个组件的状态(通信)
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
npm install redux
精简版
一个计算器的案例:
import {Component} from "react";
import {Button, ConfigProvider} from 'antd'
import {LogoutOutlined} from '@ant-design/icons';
import {withRouter} from "react-router-dom";
import './App.css';
class App extends Component {
state = {
sum: 0
}
plus = () => {
let {sum} = this.state;
sum += Number.parseInt(this.select.value);
this.setState({sum})
}
plusOdd = () => {
let {sum} = this.state;
if (sum % 2 !== 0) {
sum += Number.parseInt(this.select.value);
this.setState({sum})
}
}
subtract = () => {
let {sum} = this.state;
sum -= Number.parseInt(this.select.value);
this.setState({sum})
}
plusAsync = () => {
setTimeout(() => {
let {sum} = this.state;
sum += Number.parseInt(this.select.value);
this.setState({sum})
}, 100);
}
render() {
const {sum} = this.state;
return (
<div>
<p>{sum}</p>
<select ref={e => this.select = e}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<br/>
<br/>
<button onClick={this.plus}>+</button>
<br/>
<br/>
<button onClick={this.subtract}>-</button>
<br/>
<br/>
<button onClick={this.plusOdd}>奇+</button>
<br/>
<br/>
<button onClick={this.plusAsync}>异步+</button>
</div>
);
}
}
export default withRouter(App);
用法:
充当reducer,第一个参数为之前的值,action中为本次传入的值
export default (preState, action) => {
const {type, data} = action;
switch (type) {
case 'add':
return preState + data;
case 'subtract':
return preState - data;
default:
// 可以用来初始化
return 0;
}
}
包装,充当store:
import {legacy_createStore as createStore} from "redux";
import countReducer from './countReducer';
export default createStore(countReducer);
使用:
import {Component} from "react";
import {withRouter} from "react-router-dom";
import countStore from "./redux/countStore";
import './App.css';
class App extends Component {
componentDidMount() {
countStore.subscribe(() => {
this.setState({});
})
}
plus = () => {
countStore.dispatch({
type: 'add',
data: Number.parseInt(this.select.value)
})
}
plusOdd = () => {
if (countStore.getState() % 2 === 1) {
countStore.dispatch({
type: 'add',
data: Number.parseInt(this.select.value)
})
}
}
subtract = () => {
countStore.dispatch({
type: 'subtract',
data: Number.parseInt(this.select.value)
})
}
plusAsync = () => {
setTimeout(() => {
countStore.dispatch({
type: 'add',
data: Number.parseInt(this.select.value)
})
}, 100);
}
render() {
return (
<div>
<p>{countStore.getState()}</p>
<select ref={e => this.select = e}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<br/>
<br/>
<button onClick={this.plus}>+</button>
<br/>
<br/>
<button onClick={this.subtract}>-</button>
<br/>
<br/>
<button onClick={this.plusOdd}>奇+</button>
<br/>
<br/>
<button onClick={this.plusAsync}>异步+</button>
</div>
);
}
}
export default withRouter(App);
完整版
在精简版的案例中没有引入action creators
,在完整版中:
- 一个常量文件,写所有的
type
中指定的名称 - 一个
action creator
文件,用来生成一个action
- 一个
store
文件,用来包装一个reducer
- 一个
reducer
文件,用来处理运算
import {Component} from "react";
import {withRouter} from "react-router-dom";
import countStore from "./redux/countStore";
import './App.css';
import {addCreator, subtractCreator} from "./redux/countActionCreator";
class App extends Component {
componentDidMount() {
countStore.subscribe(() => {
this.setState({});
})
}
plus = () => {
countStore.dispatch(addCreator(Number.parseInt(this.select.value)))
}
plusOdd = () => {
if (countStore.getState() % 2 === 1) {
countStore.dispatch(addCreator(Number.parseInt(this.select.value)))
}
}
subtract = () => {
countStore.dispatch(subtractCreator(Number.parseInt(this.select.value)))
}
plusAsync = () => {
setTimeout(() => {
countStore.dispatch(addCreator(Number.parseInt(this.select.value)))
}, 100);
}
render() {
return (
<div>
<p>{countStore.getState()}</p>
<select ref={e => this.select = e}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<br/>
<br/>
<button onClick={this.plus}>+</button>
<br/>
<br/>
<button onClick={this.subtract}>-</button>
<br/>
<br/>
<button onClick={this.plusOdd}>奇+</button>
<br/>
<br/>
<button onClick={this.plusAsync}>异步+</button>
</div>
);
}
}
export default withRouter(App);
常量文件:
export const ADD = 'add';
export const SUBTRACT = 'subtract';
创建者:
import {ADD, SUBTRACT} from './constant';
export const addCreator = data => ({type: ADD, data});
export const subtractCreator = data => ({type: SUBTRACT, data});
reducer
:
import {ADD, SUBTRACT} from "./constant";
export default (preState, action) => {
const {type, data} = action;
switch (type) {
case ADD:
return preState + data;
case SUBTRACT:
return preState - data;
default:
// 可以用来初始化
return 0;
}
}
异步action
action
值可以为对象或者函数,当action
为对象时称为同步action
,如果为函数时称为异步action
,需要引入一个中间件转换redux-thunk
npm install redux-thunk
修改store
:
import {legacy_createStore as createStore, applyMiddleware} from "redux";
import thunk from 'redux-thunk';
import countReducer from './countReducer';
export default createStore(countReducer, applyMiddleware(thunk));
添加createor
:
export const addAsyncCreator = (data, time) => {
return dispatch => {
setTimeout(() => {
dispatch({
type: ADD,
data
})
}, time);
}
}
jsx
中的调用:
plusAsync = () => {
countStore.dispatch(addAsyncCreator(Number.parseInt(this.select.value), 100));
}
在上例中,可以把异步的加法的setTimeout
放到一个creator
中
react-redux
在react-redux
中可以自动检测数据的变化,无需再使用setState
进行手动更新数据
安装:
npm install react-redux
UI组件:
- 只负责 UI 的呈现,不带有任何业务逻辑
- 通过props接收数据(一般数据和函数)
- 不使用任何 Redux 的 API
- 一般保存在components文件夹下
容器组件:
- 负责管理数据和业务逻辑,不负责UI的呈现
- 使用 Redux 的 API
- 一般保存在containers文件夹下
其他的都不变,即reducer
、action
、creator
、store
不用修改,把之前在jsx
中用到的redux
剥离出来,只用来显示页面,通过容器组件传递props
的方法为其修改值
container:
import CountUI from "../component/ui/CountUI";
import {connect} from "react-redux";
import {addAsyncCreator, addCreator, subtractCreator} from "../redux/countActionCreator";
// 调用时已经将state传递过来了
const mapStateToProps = (state) => {
return {
value: state
}
}
const mapDispathToProps = dispatch => {
return {
add(num) {
dispatch(addCreator(num));
},
subtract(num) {
dispatch(subtractCreator(num))
},
addAsync(num, time) {
dispatch(addAsyncCreator(num, time));
}
}
}
// 参数1为数据,参数2为实际的操作方法
export default connect(mapStateToProps, mapDispathToProps)(CountUI);
ui:
import {Component} from "react";
import {withRouter} from "react-router-dom";
class CountUI extends Component {
componentDidMount() {
// console.log(this)
}
plus = () => {
const value = Number.parseInt(this.select.value);
this.props.add(value);
}
plusOdd = () => {
const value = Number.parseInt(this.select.value);
if (this.props.value % 2 === 1) {
this.props.add(value);
}
}
subtract = () => {
const value = Number.parseInt(this.select.value);
this.props.subtract(value);
}
plusAsync = () => {
const value = Number.parseInt(this.select.value);
this.props.addAsync(value, 200);
}
render() {
const {value} = this.props;
return (
<div>
<p>{value}</p>
<select ref={e => this.select = e}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<br/>
<br/>
<button onClick={this.plus}>+</button>
<br/>
<br/>
<button onClick={this.subtract}>-</button>
<br/>
<br/>
<button onClick={this.plusOdd}>奇+</button>
<br/>
<br/>
<button onClick={this.plusAsync}>异步+</button>
</div>
);
}
}
export default withRouter(CountUI);
app.js
import {Component} from "react";
import {withRouter} from "react-router-dom";
import './App.css';
import CountContainer from "./container/CountContainer";
import countStore from "./redux/countStore";
class App extends Component {
render() {
return (
<div>
<CountContainer store={countStore} />
</div>
);
}
}
export default withRouter(App);
精简map
在mapDispatchProps
中,react-redux
会默认的使用dispatch
进行调用
import CountUI from "../component/ui/CountUI";
import {connect} from "react-redux";
import {addAsyncCreator, addCreator, subtractCreator} from "../redux/countActionCreator";
// 参数1为数据,参数2为实际的操作方法
export default connect(
(state) => ({
value: state
}),
{
addCreator,
subtractCreator,
addAsyncCreator
}
)(CountUI);
ui:
import {Component} from "react";
import {withRouter} from "react-router-dom";
class CountUI extends Component {
componentDidMount() {
// console.log(this)
}
plus = () => {
const value = Number.parseInt(this.select.value);
this.props.addCreator(value);
}
plusOdd = () => {
const value = Number.parseInt(this.select.value);
if (this.props.value % 2 === 1) {
this.props.addCreator(value);
}
}
subtract = () => {
const value = Number.parseInt(this.select.value);
this.props.subtractCreator(value);
}
plusAsync = () => {
const value = Number.parseInt(this.select.value);
this.props.addAsyncCreator(value, 200);
}
render() {
const {value} = this.props;
return (
<div>
<p>{value}</p>
<select ref={e => this.select = e}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<br/>
<br/>
<button onClick={this.plus}>+</button>
<br/>
<br/>
<button onClick={this.subtract}>-</button>
<br/>
<br/>
<button onClick={this.plusOdd}>奇+</button>
<br/>
<br/>
<button onClick={this.plusAsync}>异步+</button>
</div>
);
}
}
export default withRouter(CountUI);
Provider标签
如果多个容器使用同一个store
,那么可以将这几个容器放到这个标签中:
<Provider store={countStore}>
<CountContainer />
</Provider>
store
是一个单例的
代码片段
-
rrc
:-
import React, {Component} from 'react'; import {connect} from 'react-redux'; function mapStateToProps(state) { return {}; } class MyCom extends Component { render() { return ( <div> </div> ); } } export default connect( mapStateToProps, )(MyCom);
-
-
rrdc
:-
import React, {Component} from 'react'; import {connect} from 'react-redux'; function mapStateToProps(state) { return {}; } function mapDispatchToProps(dispatch) { return {}; } class MyCom extends Component { render() { return ( <div> </div> ); } } export default connect( mapStateToProps, )(MyCom);
-
Reducer
和store
可以合并在一块:
store
:
import {ADD, SUBTRACT} from "./constant";
import {applyMiddleware, legacy_createStore as createStore} from "redux";
import thunk from "redux-thunk";
const countReducer = (preState, action) => {
const {type, data} = action;
switch (type) {
case ADD:
return preState + data;
case SUBTRACT:
return preState - data;
default:
// 可以用来初始化
return 0;
}
}
export default createStore(countReducer, applyMiddleware(thunk));
action:
import {ADD, SUBTRACT} from './constant';
export const addAction = data => ({type: ADD, data});
export const subtractAction = data => ({type: SUBTRACT, data});
export const addAsyncAction = (data, time) => {
return dispatch => {
setTimeout(() => {
dispatch({
type: ADD,
data
})
}, time);
}
}
组件:
import {Component} from "react";
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {addAsyncAction, addAction, subtractAction} from "../redux/countAction";
class Count extends Component {
componentDidMount() {
// console.log(this)
}
plus = () => {
const value = Number.parseInt(this.select.value);
this.props.addAction(value);
}
plusOdd = () => {
const value = Number.parseInt(this.select.value);
if (this.props.value % 2 === 1) {
this.props.addAction(value);
}
}
subtract = () => {
const value = Number.parseInt(this.select.value);
this.props.subtractAction(value);
}
plusAsync = () => {
const value = Number.parseInt(this.select.value);
this.props.addAsyncAction(value, 200);
}
render() {
const {value} = this.props;
return (
<div>
<p>{value}</p>
<select ref={e => this.select = e}>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
</select>
<br/>
<br/>
<button onClick={this.plus}>+</button>
<br/>
<br/>
<button onClick={this.subtract}>-</button>
<br/>
<br/>
<button onClick={this.plusOdd}>奇+</button>
<br/>
<br/>
<button onClick={this.plusAsync}>异步+</button>
</div>
);
}
}
export default connect(
(state) => ({
value: state
}),
{
addAction,
subtractAction,
addAsyncAction
}
)(withRouter(Count));
多个Reducer
多个reducer可以交给同一个store
管理,需要通过combineReducers
函数组合在一起
combine
中文为组合、混合、合并,读音为kəmˈbaɪn
import {legacy_createStore as createStore, combineReducers} from "redux";
import {personReducer} from "./reducer/personReducer";
import {countReducer} from "./reducer/countReducer";
const allReducer = combineReducers({
personReducer,
countReducer
})
export default createStore(allReducer)
此时,代码结构:
action
文件夹,保存所有的action
reducer
文件夹,保存所有的reducer
constant.js
文件,保存所有的常量store.js
文件,组合所有的reducer
组件中引入时,引入action
的方法和之前一样,但数据需要分别引入:
-
reducer
文件夹-
countReducer
-
import {ADD_COUNT} from "../constant"; export const countReducer = (preState = 0, action) => { const {type, data} = action; switch (type) { case ADD_COUNT: return preState + data; default: return preState; } }
-
-
personReducer
-
import {ADD_PERSON} from "../constant"; export const personReducer = (preState = [], action) => { const {data, type} = action; switch (type) { case ADD_PERSON: return [...preState, data]; default: return preState; } }
-
-
-
action
文件夹-
countAction
-
import {ADD_COUNT} from "../constant"; export const addAction = (data) => { return { type: ADD_COUNT, data } }
-
-
personAction
-
import {ADD_PERSON} from "../constant"; export const addPersonAction = (data) => { return { data, type: ADD_PERSON } }
-
-
-
constant.js
文件-
export const ADD_PERSON = 'addPerson'; export const ADD_COUNT = 'addCount';
-
-
store.js
文件-
import {legacy_createStore as createStore, combineReducers} from "redux"; import {personReducer} from "./reducer/personReducer"; import {countReducer} from "./reducer/countReducer"; const allReducer = combineReducers({ personReducer, countReducer }) export default createStore(allReducer)
-
-
组件:
-
count
-
import React, {Component} from 'react'; import {connect} from 'react-redux'; import {addAction} from "../../redux/action/countAction"; function mapStateToProps(state) { return { count: state.countReducer, size: state.personReducer.length }; } class Count extends Component { add = () => { this.props.addAction(Number.parseInt(this.c.value)); } render() { const {count, size} = this.props; return ( <div> <p>当前:{count}</p> <input ref={e => this.c = e} type="text"/> <br/> <br/> <button onClick={this.add}>增加</button> <p>Person人数:{size}</p> </div> ); } } export default connect( mapStateToProps, { addAction } )(Count);
-
-
person
-
item
-
import React, {Component} from 'react'; class Item extends Component { render() { const {name, size, more} = this.props; return ( <div> <p>名称:{name},大小:{size},备注:{more}</p> </div> ); } } export default Item;
-
-
manager
-
import React, {Component} from 'react'; import {connect} from 'react-redux'; import {addPersonAction} from "../../redux/action/personAction"; function mapStateToProps(state) { return {}; } class Manager extends Component { p = {} componentDidMount() { } addPerson = () => { const person = { name: this.p.name.value, size: this.p.size.value, more: this.p.more.value, id: new Date().getTime() } this.props.addPersonAction(person); } render() { return ( <div> 名称:<input ref={c => this.p.name = c} type="text"/> <br/> 大小:<input ref={c => this.p.size = c} type="text"/> <br/> 备注:<input ref={c => this.p.more = c} type="text"/> <br/> <br/> <button onClick={this.addPerson}>添加</button> </div> ); } } export default connect( mapStateToProps, { addPersonAction } )(Manager);
-
-
person
-
import React, {Component} from 'react'; import {connect} from 'react-redux'; import Manager from "./Manager"; import Item from "./Item"; function mapStateToProps(state) { return { arr: state.personReducer, count: state.countReducer }; } class Person extends Component { componentDidMount() { } render() { const {arr, count} = this.props; return ( <div> <h1>Person</h1> <p>count = {count}</p> <Manager/> {arr.map((it) => <Item key={it.id} {...it} />)} </div> ); } } export default connect( mapStateToProps, )(Person);
-
-
-
-
app.js
-
import {Component} from "react"; import {withRouter} from "react-router-dom"; import './App.css'; import {Provider} from "react-redux"; import store from "./redux/store"; import Person from "./component/person/Person"; import Count from "./component/count/Count"; class App extends Component { render() { return ( <div> <Provider store={store}> <Count /> <hr/> <Person/> </Provider> </div> ); } } export default withRouter(App);
-
注意事项
如果在一个数组中删除某个元素,使用以下方式:
export const personReducer = (preState = [], action) => {
const {data, type} = action;
switch (type) {
case ADD_PERSON:
return [...preState, data];
case REMOVE_PERSON:
let index = 0;
for (let i = 0; i < preState.length; i++) {
if (preState[i].id === data) {
index = i;
break;
}
}
preState.splice(index, 1)
return preState;
default:
return preState;
}
}
会出现页面无法更新的情况,但数组中的元素确实被删除了,但redux
认为没有更新,因为会比较当前返回的元素和**之前的元素preState
**的地址,如果两个地址一样就认为没有发生数据更新
纯函数
一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)
必须遵守以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备,也就是不靠谱的事不能做,因为网络请求可能会发生错误
- 不能调用Date.now()或者Math.random()等不纯的方法
redux的reducer函数必须是一个纯函数
扩展
安装redux-devtools
扩展
项目中:
npm install redux-devtools-extension
修改store
:
import {legacy_createStore as createStore, combineReducers} from "redux";
import {personReducer} from "./reducer/personReducer";
import {countReducer} from "./reducer/countReducer";
import {composeWithDevTools} from "redux-devtools-extension";
const allReducer = combineReducers({
personReducer,
countReducer
})
export default createStore(allReducer, composeWithDevTools())
懒加载
路由组件经常做懒加载,默认情况下页面加载完毕,所有的组件都会被引入,如果某个路由组件特别大,会造成页面首次加载时的白屏时间过长,可以使用懒加载
suspense中文为担心、焦虑,读音为səˈspens
import {Component, lazy, Suspense} from "react";
import {Link, Route, withRouter} from "react-router-dom";
import './App.css';
const Home = lazy(() => import('./component/Home'))
const About = lazy(() => import('./component/About'))
class App extends Component {
state = {
count: 0
}
plus = () => {
this.setState((state, props) => {
return {
count: state.count + 1
}
}, () => {
console.log(this.state)
})
}
render() {
const {count} = this.state;
return (
<div>
<Link to={'/home'}>Home</Link>
<br/>
<Link to={'/about'}>about</Link>
<Suspense fallback={<h1>加载中...</h1>}>
<Route path={'/home'} component={Home} />
<Route path={'/about'} component={About} />
</Suspense>
</div>
);
}
}
export default withRouter(App);
- 路由组件的引入使用第5、6行的方式,第31行中指定了当组件加载时所显示的内容,可以放一个加载中的动画
Hooks
React 16.8新增的特性,可以在函数组件中使用一些类式组件才能够使用的特性
useState
使得函数组件使用state
,按理说,页面更新时将会重新调用这个函数,当重新调用这个函数时,第4行将会被调用 会将num的值设置为0,但事实上并没有这个现象,这是因为底层做了处理,把num存了下来
import React, {useState} from 'react';
function Count(props) {
const [num, setNum] = useState(0);
return (
<div>
{num}
<br/>
<button onClick={() => setNum(num + 1)}>+1</button>
</div>
);
}
export default Count;
也可以传入一个函数,函数的参数为之前的值:
import React, {useState} from 'react';
function Count(props) {
const [num, setNum] = useState(0);
const plus = () => {
setNum((pre) => {
console.log(pre)
return pre + 1;
})
}
return (
<div>
{num}
<br/>
<button onClick={plus}>+1</button>
</div>
);
}
export default Count;
useEffect
effect
中文为影响、效果、印象,读音为ɪˈfekt
,可以在函数组件中使用生命周期
有两个参数,第一个参数为一个回调,第二个参数为数组:
- 参数2数组中内容可以为通过
useState
创建的对象 - 第二个参数代表检测数组中的对象值的变化,当某个对象变化了将会调用参数1的回调
- 如果不提供数组,代表检测所有值的变化
- 如果提供空数组,代表不检测任何值的变化,此时仅在组件加载时调用此回调
自动计数例子:
import React, {useEffect, useState} from 'react';
function Count(props) {
const [num, setNum] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setNum( num => num + 1);
}, 1000);
return () => {
clearInterval(interval);
console.log("此处相当于componentDidUnmounted")
}
}, []);
const plus = () => {
setNum(num + 1);
}
return (
<div>
{num}
<br/>
<button onClick={plus}>+1</button>
</div>
);
}
export default Count;
在 React
中,由于 setState
是异步执行的,所以直接修改 state
值可能会导致出现一些不可预测的行为。为了避免这种情况,React 提供了函数式更新的方式,即在 setNum
中传入一个函数,以确保更新 state
时使用最新的值。
因此,将代码中的 setNum(num + 1)
修改为 setNum(num => num + 1)
,这样每次调用 setNum
都会使用最新的 num
值进行更新。
可以看出,useEffect
可以模拟componentDidMounted
、componentDidUpdate
、componentDidUnmounted
三个生命周期的效果
useRef
import React, {useEffect, useRef, useState} from 'react';
function Count(props) {
const content = useRef();
const getContent = () => {
console.log(content.current.value)
}
return (
<div>
<input ref={content} type="text"/>
<br/>
<button onClick={getContent}>获取内容</button>
</div>
);
}
export default Count;
fragment
React
中不允许多个根标签,在之前的解决方案中,使用了<div></div>
作为一个根标签,但在最后生成的HTML
文件中包含了一级又一级的<div></div>
,可以使用<Fragment></Fragment>
代表一个根标签,这个根标签不会在最后的HTML
文件中显示出来
也可以使用<></>
空标签代替<Fragment></Fragment>
,但空标签不能够写任何属性,<Fragment></Fragment>
可以写一个key
属性
PureComponent
在之前的React.Component
中存在着效率低的问题,只要进行了setState
,无论数据是否更新,都会调用render()
,即使子组件没有用到父组件的数据,也会进行render()
使用PureComponent
也会带来一些副作用,如果:
const obj = this.state.obj;
obj.aaa = 'aaaa';
this.setState(obj);
时,不会导致页面更新,因为底层仅仅进行了浅比较,原理是使用了shouldComponentUpdate()
作了比较
useCallback
useCallback是一个React Hook,它可以让你缓存一个函数定义,避免在每次重新渲染时创建一个新的函数。要想使用useCallback,你需要做以下几步:
- 在组件中调用useCallback函数,并传入两个参数:一个是你想要缓存的函数,另一个是一个依赖数组例如:
import React, { useCallback } from 'react'
function MyComponent() {
// 缓存一个函数,接受一个name参数,并打印它
const sayHello = useCallback(name => {
console.log('Hello', name)
}, [])
// ...其他逻辑
}
- useCallback函数会返回一个缓存的函数,你可以在组件中任何地方使用它
- 例如:
import React, { useCallback } from 'react'
function MyComponent() {
// 缓存一个函数,接受一个name参数,并打印它
const sayHello = useCallback(name => {
console.log('Hello', name)
}, [])
// 调用缓存的函数
sayHello('React')
}
- useCallback函数会根据依赖数组中的值来决定是否返回之前缓存的函数,还是返回当前传入的函数,如果依赖数组中的所有值都没有变化,useCallback会返回之前缓存的函数;如果依赖数组中的任何值有变化,useCallback会返回当前传入的函数,并把它缓存起来,以便下次使用。例如:
import React, { useState, useCallback } from 'react'
function MyComponent() {
// 定义一个state变量count
const [count, setCount] = useState(0)
// 缓存一个函数,接受一个name参数,并打印它和count的值
// 把count作为依赖数组中的唯一值
const sayHello = useCallback(name => {
console.log('Hello', name, count)
}, [count])
// 当count变化时,useCallback会返回当前传入的函数,并缓存它
// 当count不变时,useCallback会返回之前缓存的函数
return (
<div>
<button onClick={() => setCount(count + 1)}>Increase Count</button>
<button onClick={() => sayHello('React')}>Say Hello</button>
</div>
)
}
useMemo
可以看作是一个计算属性
import {useMemo, useRef, useState} from "react";
function App() {
const [user, setUser] = useState({
firstName: '',
lastName: ''
});
const fullName = useMemo(() => user.firstName + '-' + user.lastName, [user]);
const getValue = (name) => {
return (e) => {
setUser(user => ({...user, [name]: e.target.value}))
}
}
return (
<>
<input onChange={getValue('firstName')} type="text"/>
<input onChange={getValue('lastName')} type="text"/>
<br/>
<p>{fullName}</p>
</>
);
}
export default App;
路由(新版)
<Switch></Switch>
标签被移除掉了,<Route/>
标签必须要放在<Routes/>
下:
<Routes>
<Route path={'/home'} element={<Home/>} />
<Route path={'/about'} element={<About />} />
</Routes>
当某个路径没有被路由匹配到时,控制台会给出No routes matched location "路径"
的警告
Navigate
可以用来跳转页面,可以理解为重定向
import React, {useState} from 'react';
import {Navigate} from "react-router-dom";
function Home(props) {
const [sum, setSum] = useState(0);
return (
<>
<p>home</p>
<p onClick={() => setSum(a => a + 1)}>{sum}</p>
<p>当num > 10时,将会跳转到about</p>
{
sum > 10 ? <Navigate to={'/about'} /> : <p>此时值小于10</p>
}
</>
);
}
export default Home;
可以指定模式:<Navigate to={'/about'} replace />
useRoutes
也可以和navigate
结合起来
import {Link, useRoutes} from "react-router-dom";
import Home from "./page/home";
import About from "./page/about";
import Person from "./page/person";
function App() {
const element = useRoutes([
{
path: '/home',
element: <Home/>
},
{
path: '/about',
element: <About/>
},
{
path: '/person',
element: <Person/>
}
])
return (
<>
<Link to={'/home'}>Home</Link>
<br/>
<Link to={'/about'}>About</Link>
<div style={{
width: 300,
height: 200,
border: '1px solid black'
}}>
{element}
</div>
</>
);
}
export default App;
通常将路由文件抽取出来放到一个单独的文件中
Outlet
import Home from "../page/home";
import About from "../page/about";
import My from "../page/my";
import More from "../page/more";
import Person from "../page/person";
export default [
{
path: '/home',
element: <Home/>
},
{
path: '/about',
element: <About/>,
children: [
{
path: 'my',
element: <My />
},
{
path: 'more',
element: <More />
}
]
},
{
path: '/person',
element: <Person/>
}
]
在<Link to="路径"></Link>
中,可以不用写全路径了,直接写路径代表从父路径匹配
import React from 'react';
import {Link, Outlet} from "react-router-dom";
function About(props) {
return (
<>
<p>about</p>
<Link to={'my'}>我的</Link> <Link to={'more'}>更多</Link>
<div style={{
width: 200,
height: 100,
border: '1px solid red',
margin: 'auto'
}}>
<Outlet />
</div>
</>
);
}
export default About;
useParams
{
path: '/person',
element: <Person/>,
children: [
{
path: ':id/:title/:content',
element: <Message />
}
]
}
用法:
import React from 'react';
import {useParams} from "react-router-dom";
function Message(props) {
const {id, title, content} = useParams();
return (
<>
<ul>
<li>{id}</li>
<li>{title}</li>
<li>{content}</li>
</ul>
</>
);
}
export default Message;
useSearchParams
可以把query中的参数取出来
import React from 'react';
import {Link, Outlet} from "react-router-dom";
const arr = [
{
id: 1,
title: '这是标题',
content: '这是内容'
},
{
id: 2,
title: '这是标题222',
content: '这是内容222'
},
{
id: 3,
title: '这是标题333',
content: '这是内容333'
},
]
function Person(props) {
return (
<>
<p>person</p>
{
arr.map(it => <p key={it.id}><Link to={`message?id=${it.id}&title=${it.title}&content=${it.content}`}>{it.title}</Link></p>)
}
<Outlet />
</>
);
}
export default Person;
import React from 'react';
import {useSearchParams} from "react-router-dom";
function Message(props) {
const [search, setSearch] = useSearchParams();
const id = search.get('id');
const title = search.get('title');
const content = search.get('content');
return (
<>
<ul>
<li>{id}</li>
<li>{title}</li>
<li>{content}</li>
</ul>
</>
);
}
export default Message;
useLocation
可以取出路径、传入的state
import React from 'react';
import {Link, Outlet} from "react-router-dom";
import message from "./message";
const arr = [
{
id: 1,
title: '这是标题',
content: '这是内容'
},
{
id: 2,
title: '这是标题222',
content: '这是内容222'
},
{
id: 3,
title: '这是标题333',
content: '这是内容333'
},
]
function Person(props) {
return (
<>
<p>person</p>
{
arr.map(it => <p key={it.id}><Link to={'message'} state={it}>{it.title}</Link></p>)
}
<Outlet />
</>
);
}
export default Person;
import React from 'react';
import {useLocation} from "react-router-dom";
function Message(props) {
const {state:{id, title, content}} = useLocation();
return (
<>
<ul>
<li>{id}</li>
<li>{title}</li>
<li>{content}</li>
</ul>
</>
);
}
export default Message;
useNavigate
可以实现编程式路由导航
import React from 'react';
import {Link, Outlet, useNavigate} from "react-router-dom";
import message from "./message";
const arr = [
{
id: 1,
title: '这是标题',
content: '这是内容'
},
{
id: 2,
title: '这是标题222',
content: '这是内容222'
},
{
id: 3,
title: '这是标题333',
content: '这是内容333'
},
]
function Person(props) {
const navigate = useNavigate();
const look = (it) => {
navigate('message', {
replace: false,
state: it
})
}
return (
<>
<p>person</p>
{
arr.map(it => <p key={it.id}>
<Link to={'message'} state={it}>{it.title}</Link>
<button onClick={() => look(it)}>查看</button>
</p>)
}
<Outlet />
</>
);
}
export default Person;
也可以用来实现前进和后退
React-Redux-Hooks
React-Redux Hooks
是 React-Redux
提供的一组 API
,可以在函数组件中订阅 Redux store 和派发 action。
React-Redux Hooks 包括以下三个 API:
useSelector()
:可以从 Redux store 中获取数据,使用一个选择器函数来指定你想要获取的数据。useDispatch()
:可以获取一个 dispatch 函数,用来派发 action 到 Redux store。useStore()
:可以获取 Redux store 的引用,用来访问 store 的状态或者替换 reducer。
使用 React-Redux Hooks 时,需要先用 <Provider>
组件包裹你的整个应用,以便让 store 在组件树中可用。
使用 React-Redux Hooks 有一些注意事项,比如:
useSelector()
默认使用 === 来比较结果,如果需要浅比较或者深比较,可以传入一个自定义的比较函数。useSelector()
不会接收 ownProps 参数,如果需要使用 props,可以通过闭包或者柯里化的方式。useDispatch()
和useStore()
只会返回同一个引用,不会在重渲染时改变。- 如果需要使用自定义的 context,可以使用
createDispatchHook()
、createSelectorHook()
和createStoreHook()
来创建对应的Hooks
。
useSelector和useDispatch
import React from 'react';
import {useDispatch, useSelector} from "react-redux";
import {removePerson} from "../redux/action/personAction";
function MyFuncPerson(props) {
const arr = useSelector(state => state.person);
const dispatch = useDispatch();
const remove = (data) => {
return () => {
dispatch(removePerson(data))
}
}
return (
<>
<ul>
{
arr.map(it => <li key={it.id}>
<span>{it.name}</span>
<button onClick={remove(it.id)}>删除</button>
</li>)
}
</ul>
</>
);
}
export default MyFuncPerson;
import React, {useRef} from 'react';
import {useDispatch} from "react-redux";
import {addPerson} from "../redux/action/personAction";
function MyFuncPersonAdd(props) {
const name = useRef();
const dispatch = useDispatch();
const add = () => {
dispatch(addPerson({
id: new Date().getTime(),
name: name.current.value
}))
}
return (
<>
<input ref={name} type="text"/>
<button onClick={add}>添加</button>
</>
);
}
export default MyFuncPersonAdd;
useStore
useStore
是一个hook
,它可以返回Redux store
对象的引用,可以用来获取store的状态或替换reducer等操作,但不会订阅store的变化。useStore不会触发组件的重新渲染,也不会执行任何比较逻辑。useStore应该被少量使用,只有在一些不常见的场景下才需要用到,比如替换reduce
Q.E.D.