React全家桶学习笔记
1. React简介
React 是 Facebook 在 2011 年开发的前端 JavaScript 库。它遵循基于组件的方法,有助于构建可重用的UI组件。
它使用虚拟DOM而非真实DOM
虚拟DOM与真实DOM比较:
- 虚拟DOM本质是一个js的对象
- 虚拟DOM比较“轻”,真实DOM比较“重”(自带属性很多)
- 虚拟DOM最终会被转化为真实DOM
2. JSX语法规则
JSX 是JavaScript XML 的简写。是 React 使用的一种文件。
- 定义虚拟DOM时,不要写引号
- 标签中混入JS表达式时要用{ }
- 样式的类名指定不要用class,要用className
- 内联样式要用**{{key: value}}**形式去写, 外部花括号表示里面要写js表达式, 内层花括号表示对象
- 虚拟DOM必须只有一个根标签
- 标签必须闭合
- 标签首字母
- 若小写字母,则React会将标签转为html同名元素
- 若大写字母开头,则react则去渲染同名组件
1<script type="text/babel">
2 const myID = "title";
3 const myData = "Hello React";
4
5 // 1. 创建虚拟DOM
6 const VDOM = ( /* 不写引号 */
7 <div>
8 <h1 className="title" id ={myID}>
9 <span style={{color: "white", fontSize:"30px"}}>{myData}</span>
10 </h1>
11 <h1 className="title" id ={myID + '1'}>
12 <span style={{color: "white", fontSize:"30px"}}>{myData}</span>
13 </h1>
14 <input type="text"></input>
15 </div>
16 )
17 // 2. 渲染虚拟DOM到页面
18 ReactDOM.render(VDOM, document.getElementById('test'));
19</script>
3. React面向组件编程
3.1. 函数式组件
1<script type="text/babel">
2 // 1. 创建函数式组件
3 function Demo(){
4 return <h1>Hello React</h1>
5 }
6
7 ReactDOM.render(<Demo/>, document.getElementById("test"));
8</script>
3.2 类式组件
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Demo2 extends React.Component{
4 render(){
5 return <h1>Hello React!!!</h1>
6 }
7 }
8 // 2. 渲染组件到页面
9 ReactDOM.render(<Demo2/>, document.getElementById("test"));
10</script>
4. React组件三大核心属性
4.1. state
状态(state)不可直接更改,需要借助setState进行更新,且更新是合并的形式,不是替换。
setState()以后组件会调用render()重新渲染
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Weather extends React.Component{
4 constructor(props){
5 super(props);
6 // 初始化状态
7 this.state = {
8 isHot: true
9 };
10 // 改变this指向
11 this.changeWeather = this.changeWeather.bind(this);
12 }
13 render(){
14 return <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
15 }
16 changeWeather(){
17 // 由于changeWeather是作为onclick的回调,不是通过实例调用的,是直接调用
18 // 类中的方法默认开启局部严格模式
19 // 所以this为 undefined
20 const isHot = this.state.isHot;
21 this.setState({
22 isHot: !isHot
23 });
24 }
25 }
26 // 2. 渲染组件到页面
27 ReactDOM.render(<Weather/>, document.getElementById("test"));
28</script>
state简化写法:
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Weather extends React.Component{
4 // 初始化状态
5 state = {
6 isHot: true,
7 wind: '微风'
8 };
9
10 render(){
11 return <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}, { this.state.wind }</h1>
12 }
13
14 // 自定义方法
15 // 箭头函数this取决于外部this
16 changeWeather = ()=>{
17 const isHot = this.state.isHot;
18 this.setState({
19 isHot: !isHot,
20 });
21 }
22 }
23 // 2. 渲染组件到页面
24 ReactDOM.render(<Weather/>, document.getElementById("test"));
25</script>
4.2. props
props用于外部传值,一但定义不允许在组件内进行更改.
组件标签内以属性定义方式进行传值
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Person extends React.Component{
4 render(){
5 console.log(this);
6 return (
7 <ul>
8 <li>姓名: {this.props.name}</li>
9 <li>性别: 女</li>
10 <li>年龄: 18</li>
11 </ul>
12 )
13 }
14 }
15 // 2. 渲染组件到页面
16 ReactDOM.render(<Person name="Jin"/>, document.getElementById("test"));
17</script>
批量传递标签属性:
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Person extends React.Component{
4 render(){
5 console.log(this);
6 return (
7 <ul>
8 <li>姓名: {this.props.name}</li>
9 <li>性别: {this.props.sex}</li>
10 <li>年龄: {this.props.age}</li>
11 </ul>
12 )
13 }
14 }
15 const p = {name: "jin", sex:"男", age: 19};
16 // 2. 渲染组件到页面
17 ReactDOM.render(<Person {...p}/>, document.getElementById("test"));
18</script>
props简写形式,标签类型限制,默认属性定义:
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Person extends React.Component{
4 // 对标签属性进行限制
5 static propTypes = {
6 name: PropTypes.string.isRequired, // 限制必传 且为字符串
7 sex: PropTypes.string,
8 age: PropTypes.number,
9 speak: PropTypes.func,
10 };
11 // 指定默认标签属性
12 static defaultProps = {
13 sex: "男",
14 age: 100
15 }
16
17 render(){
18 console.log(this);
19 return (
20 <ul>
21 <li>姓名: {this.props.name}</li>
22 <li>性别: {this.props.sex}</li>
23 <li>年龄: {this.props.age}</li>
24 </ul>
25 )
26 }
27 }
28 const p = {name: "jin", sex:"男", age: 19};
29 // 2. 渲染组件到页面
30 ReactDOM.render(<Person {...p}/>, document.getElementById("test"));
31 ReactDOM.render(<Person name="Tom" sex="woman"/>, document.getElementById("test1"));
32</script>
constructor中props问题
1// 不接受props 构造器中可能获取不到props值.
2// constructor(){
3// super();
4// console.log(this.props); ------- undefined
5// }
6constructor(props){
7 super(props);
8 console.log(this.props);
9}
4.3. refs
组件内的html标签可以定义ref属性来标识自己,React会将其收集到组件实例的refs属性内.
利用refs可以获取到ref属性标识的dom节点
不应该过度使用ref
-
字符串形式ref (不推荐)
1<script type="text/babel"> 2 // 1. 创建类式组件 3 class Demo extends React.Component{ 4 render(){ 5 return ( 6 <div> 7 <input ref="input1" type="text" placeholder="点击按钮提示数据" /> 8 <button ref="btn1" onClick={this.showData}>点我提示左侧数据</button> 9 <input ref="input2" type="text" placeholder="失去焦点提示数据" /> 10 </div> 11 ) 12 } 13 14 showData = ()=>{ 15 console.dir(this.refs.input1); 16 } 17 } 18 // 2. 渲染组件到页面 19 ReactDOM.render(<Demo />, document.getElementById("test")); 20</script>
-
回调形式ref
如果回调函数是以内联方式定义的,则在组件更新过程中它会被执行两次,第一次传入参数为null,第二次会传入参数DOM元素.(不是啥大问题)
通过将ref的回调函数定义成class的绑定函数(实例方法)可以避免上述问题.
1<script type="text/babel"> 2 // 1. 创建类式组件 3 class Demo extends React.Component{ 4 state = { 5 isHot: true 6 } 7 render(){ 8 console.log(this); 9 return ( 10 <div> 11 <input ref={(currentNode)=>{ 12 // 回调参数为dom节点本身 13 this.input1 = currentNode; 14 console.log('被调用'); 15 }} 16 type="text" placeholder="点击按钮提示数据" /> 17 <button ref={(currentNode)=>{ 18 // 回调参数为dom节点本身 19 this.btn1 = currentNode; 20 }} 21 onClick={this.showData}>点我提示左侧数据</button> 22 <input ref={(currentNode)=>{ 23 // 回调参数为dom节点本身 24 this.input2 = currentNode; 25 }} 26 type="text" placeholder="失去焦点提示数据" /> 27 <br /> 28 <span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}!!</span> 29 </div> 30 ) 31 } 32 33 showData = ()=>{ 34 console.log(this.input1.value); 35 const { isHot } = this.state; 36 this.setState({isHot: !isHot}); 37 } 38 } 39 // 2. 渲染组件到页面 40 ReactDOM.render(<Demo />, document.getElementById("test")); 41</script>
-
createRef
React.createRef()函数调用后返回一个容器, 该容器用于存储ref所标识的dom节点, 需要多少个节点就创建多少个ref容器
1<script type="text/babel"> 2 // 1. 创建类式组件 3 class Demo extends React.Component{ 4 /* 5 React.createRef()调用后可以返回一个容器,该容器用于存储ref所标识的dom节点 6 该容器只允许存一个节点 7 */ 8 input1 = React.createRef(); 9 render(){ 10 return ( 11 <div> 12 <input ref={this.input1} type="text" placeholder="点击按钮提示数据" /> 13 <button ref="btn1" onClick={this.showData}>点我提示左侧数据</button> 14 <input ref="input2" type="text" placeholder="失去焦点提示数据" /> 15 </div> 16 ) 17 } 18 19 showData = ()=>{ 20 console.log(this.input1); 21 } 22 } 23 // 2. 渲染组件到页面 24 ReactDOM.render(<Demo />, document.getElementById("test")); 25</script>
5. React中的事件处理
-
通过onXxx属性指定事件处理函数(注意大小写)
a. React使用的是自定义(合成)事件,而不是使用原生DOM事件 ————为了更好的兼容性
b. React中的事件是通过事件委托方式处理的(委托给最外层元素) ————为了高效
-
通过event.target得到发生事件的DOM元素对象
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Demo extends React.Component{
4 input1 = React.createRef();
5 render(){
6 console.log(this);
7 return (
8 <div>
9 <input ref={this.input1} type="text" placeholder="点击按钮提示数据" />
10 <button onClick={this.showData}>点我提示左侧数据</button>
11 <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
12 </div>
13 )
14 }
15
16 showData = ()=>{
17 console.log(this.input1.current.value);
18 }
19
20 showData2 = (event)=>{
21 console.log(event.target.value);
22 }
23 }
24 // 2. 渲染组件到页面
25 ReactDOM.render(<Demo />, document.getElementById("test"));
26</script>
5.1 非受控组件
顾名思义,非受控组件即不受状态控制,获取数据即操作DOM,即用即取
以做一个用户登录表单提交为例
实现方法可以有利用ref获取dom节点,在登录提交时获取对应节点值,但ref不建议过度使用
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Login extends React.Component{
4 render(){
5 return (
6 <form onSubmit={this.handleSubmit}>
7 用户名: <input ref={(c)=>this.username = c} type="text" name="username"/>
8 密码: <input ref={(c)=>this.password = c} type="password" name="password"/>
9 <button> 登录 </button>
10 </form>
11 )
12 }
13
14 handleSubmit = (event)=>{
15 event.preventDefault();
16 console.log(this.username.value, this.password.value);
17 }
18 }
19 // 2. 渲染组件到页面
20 ReactDOM.render(<Login />, document.getElementById("test"));
21</script>
5.2 受控组件
受控组件必须要有value和onChange,onChange用于获取value,保存在状态中,在需要时从状态中去获取值
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Login extends React.Component{
4 state = {
5 username: "",
6 password: ""
7 }
8 render(){
9 console.log(this);
10 return (
11 <form onSubmit={this.handleSubmit}>
12 用户名: <input value="111" onChange={this.saveUsername} type="text" name="username"/>
13 密码: <input onChange={this.savePassword} type="password" name="password"/>
14 <button> 登录 </button>
15 </form>
16 )
17 }
18
19 handleSubmit = (event)=>{
20 event.preventDefault();
21 console.log(this.username.value, this.password.value);
22 }
23
24 saveUsername = (event)=>{
25 console.log(event.target.value);
26 this.setState({username: event.target.value});
27 }
28
29 savePassword = ()=>{
30 this.setState({password: event.target.value});
31 }
32 }
33 // 2. 渲染组件到页面
34 ReactDOM.render(<Login />, document.getElementById("test"));
35</script>
5.3 函数柯里化
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
上述onChange每一个事件都要绑定一个不同函数,显然面对多条input时会造成代码冗余
因此,可以利用函数闭包,接收数据类型参数,返回真正调用的函数,根据数据类型参数来对对应状态进行赋值
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Login extends React.Component{
4 state = {
5 username: "",
6 password: ""
7 }
8 render(){
9 console.log(this);
10 return (
11 <form onSubmit={this.handleSubmit}>
12 用户名: <input onChange={this.saveFormData('username')} type="text" name="username"/>
13 密码: <input onChange={this.saveFormData('password')} type="password" name="password"/>
14 <button> 登录 </button>
15 </form>
16 )
17 }
18
19 handleSubmit = (event)=>{
20 event.preventDefault();
21 console.log(this.state.username, this.state.password);
22 }
23
24 saveFormData = (dataType)=>{
25 // this.setState({username: event.target.value});
26 return (event)=>{
27 console.log(dataType);
28 console.log(event.target.value);
29 this.setState({[dataType]: event.target.value});
30 }
31 }
32 }
33 // 2. 渲染组件到页面
34 ReactDOM.render(<Login />, document.getElementById("test"));
35</script>
6. React组件的生命周期
React组件包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。
6.1. React生命周期(旧)
初始化阶段 —— 由React.render()触发
-
constructor
构造函数构建组件实例
-
组件将要被挂载
componentWillMount(){}
-
初始化渲染
render()
-
组件挂载完毕
componentDidMount(){}
注意:比较常用,一般用于做一些初始化的事,例如:开启定时器,发送网络请求,订阅消息
-
组件即将卸载
componentWillUnmount(){}
注意:比较常用,一般用于做一些收尾的事,例如:关闭定时器,取消订阅消息
-
卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById(“test”))
Update
-
componentWillReceiveProps(props)
组件将要接收到props,触发后续更新
注意:第一次接收到props不会触发
-
setState()
设置state,触发后续更新
-
forceUpdate()
强制更新, 比正常跟新少了一个shouldUpdate
-
组件是否应该更新
shouldComponentUpdate(nextProps, nextState){}
-
组件将要更新
componentWillUpdate(){}
-
组件更新完毕
componentDidUpdate(){}
1<script type="text/babel">
2 // 1. 创建类式组件
3 class Count extends React.Component{
4 constructor(props){
5 console.log("constructor");
6 super(props);
7 this.state = {
8 count: 0
9 }
10 }
11
12 componentWillMount(){
13 console.log("componentWillMount");
14 }
15
16 componentDidMount(){
17 console.log("componentDidMount");
18 }
19
20 componentWillUnmount(){
21 console.log("componentWillUnmount");
22 }
23
24 shouldComponentUpdate(){
25 console.log("shouldComponentUpdate");
26 return true;
27 }
28
29 componentWillUpdate(){
30 console.log("componentWillUpdate");
31 }
32
33 componentDidUpdate(){
34 console.log("componentDidUpdate");
35 }
36
37 render(){
38 console.log("Render");
39 return (
40 <div>
41 <h2>当前求和: {this.state.count}</h2>
42 <button onClick={this.add}>点我加1</button>
43 <button onClick={this.death}>卸载组件</button>
44 <button onClick={this.force}>强制更新</button>
45 </div>
46 )
47 }
48
49 add = ()=>{
50 let count = this.state.count;
51 this.setState({count: count + 1})
52 }
53
54 death = ()=>{
55 ReactDOM.unmountComponentAtNode(document.getElementById("test"));
56 }
57
58 force = ()=>{
59 this.forceUpdate();
60 }
61 }
62
63 class A extends React.Component{
64 state = {
65 carName: "奔驰"
66 }
67
68 render(){
69 return (
70 <div>
71 <div>我是A组件</div>
72 <button onClick={this.changeCar}>换车</button>
73 <B carName={this.state.carName}/>
74 </div>
75 )
76 }
77
78 changeCar = ()=>{
79 let carName = this.state.carName;
80 this.setState({carName: carName == "奔驰" ? "奥拓" : "奔驰"});
81 }
82 }
83
84 class B extends React.Component{
85 componentWillReceiveProps(props){
86 console.log("componentWillReceiveProps", props);
87 }
88
89 render(){
90 return (
91 <div>我是B组件,接收到的车是{this.props.carName}</div>
92 )
93 }
94 }
95 // 2. 渲染组件到页面
96 ReactDOM.render(<A />, document.getElementById("test"));
97</script>
6.2. React生命周期(新)
新版本生命周期和旧版本没太大区别,主要区别如下:
- componentWillMount()
- componentWillUpdate()
- componentWillReceiveProps()
以上三个在新版本中需要加上UNSAFE_前缀使用,如UNSAFE_componentWillMount()
这里的UNSAFE不是指安全性,而是指使用这些生命周期函数的代码在未来版本中可能会出现bug,React未来打算实现异步渲染。
同时新增了下列两个生命周期函数:
-
getDerivedStateFromProps()
: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。返回一个state状态对象或者null,根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。此方法适用于一些极其特殊的时刻,state的值在任何时候都取决于props
-
getSnapshotBeforeUpdate()
: 在更新时调用,最近一次渲染输出(提交到 DOM 节点)之前调用,获取快照,此函数任何返回值都将作为参数传递给componentDidUpdate()
,此方法也不常用
新版Update调用顺序:
getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate(preProps, preState, snapshotValue)
getSnapshotBeforeUpdate()
用法示例:
1<script type="text/babel">
2 // 1. 创建类式组件
3 class NewsList extends React.Component{
4 state = {
5 newsArr:[]
6 }
7
8 componentDidMount(){
9 setInterval(() => {
10 const newsArr = this.state.newsArr;
11 const news = '新闻' + (newsArr.length + 1);
12 this.setState({newsArr : [news, ...newsArr]});
13 }, 1000);
14 }
15
16 getSnapshotBeforeUpdate(){
17 // 返回更改前内容区高度
18 return this.refs.list.scrollHeight;
19 }
20
21 componentDidUpdate(preProps, preState, snapshotVal){
22 this.refs.list.scrollTop += this.refs.list.scrollHeight - snapshotVal;
23 }
24
25 render(){
26 return(
27 <div className="list" ref="list">
28 {
29 this.state.newsArr.map((val, index)=>{
30 return <div className="news" key={index}>{val}</div>
31 })
32 }
33 </div>
34 )
35 }
36 }
37 // 2. 渲染组件到页面
38 ReactDOM.render(<NewsList />, document.getElementById("test"));
39</script>
7. DOM的diff算法
-
虚拟DOM中key的作用
a. 简单地说,key是虚拟dom对象的标识
b. 当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟dom】随后,React进行【新虚拟dom】和【旧虚拟dom】的diff比较
比较规则下:
-
旧虚拟dom中找到了与新虚拟dom相同的key
若虚拟dom的内容没变,则直接使用之前的真实dom。
若虚拟dom的内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom
-
就虚拟dom中没有找到与新的虚拟dom相同的key
根据数据创建新的真实dom,随后渲染到页面
-
-
用index作为key可能会引发的问题
a. 若对数据进行:逆序添加,逆序删除等破坏顺序的操作(例如,在数组首位添加删除元素):
会产生没有必要的真实DOM的更新(因为index变了)
b. 若结构中还包含输入类的dom:
会产生错误DOM更新
c. 若仅用于渲染列表用于展示,则用index没有问题,但是数据量大进行逆序操作会引发效率问题
8. React脚手架使用
8.1. 创建项目并启动
-
全局安装
npm install -g create-react-app
-
创建项目
create-react-app hello-react
-
启动项目
npm start
-
打包项目
npm run build
React项目内组件一般用jsx后缀结尾(用js也能用,一般为了区分)
9. React父子组件通信
9.1. 父组件给子组件传递数据
通过props方式传递
9.2. 子组件给父组件传递数据
也是通过props方式,但是父组件要先定义一个修改数据的函数,将该函数传递给子组件,子组件调用函数对父组件数据进行修改
1class App extends React.Component{
2 state = {
3 todos:[
4 {id: '001', name: '吃饭' , done: true},
5 {id: '002', name: '睡觉' , done: true},
6 {id: '003', name: '敲代码' , done: false},
7 {id: '004', name: '逛街' , done: true}
8 ]
9 }
10
11 addTodo = (todo)=>{
12 this.setState({todos: [...this.state.todos, todo]});
13 }
14
15 deleteTodo = (id)=>{
16 var newTodos = this.state.todos.filter((todo)=>{
17 return todo.id !== id;
18 })
19 this.setState({todos: newTodos});
20 }
21
22 changeTodo = (id, done)=>{
23 const newTodos = this.state.todos.map((todo)=>{
24 if(todo.id === id){
25 todo.done = done;
26 }
27 return todo;
28 })
29 this.setState({todos: newTodos});
30 }
31
32 checkAllTodos = (done)=>{
33 const {todos} = this.state;
34 const newTodos = todos.map((todo)=>{
35 return {...todo, done: done};
36 })
37 this.setState({todos: newTodos});
38 }
39
40 clearAllDone = ()=>{
41 const {todos} = this.state;
42 const newTodos = todos.filter((todo)=>{
43 return !todo.done;
44 })
45 this.setState({todos: newTodos});
46 }
47
48 render(){
49 return (
50 <div className="todo-container">
51 <div className="todo-wrap">
52 <Header addTodo={this.addTodo}></Header>
53 <List todos={this.state.todos} changeTodo={this.changeTodo} deleteTodo={this.deleteTodo}></List>
54 <Footer todos={this.state.todos} checkAllTodos={this.checkAllTodos} clearAllDone={this.clearAllDone}> </Footer>
55 </div>
56 </div>
57 )
58 }
59}
10. React兄弟组件通信
消息订阅与发布
工具库: PubSubJS
适用于任意组件间通信
1import PubSub from 'pubsub-js'
2
3// 消息订阅 用于需要获取数据的组件
4var token = PubSub.subscribe('MY TOPIC', mySubscriber);
5
6// 消息发布 用于需要传递数据的组件
7PubSub.publish('MY TOPIC', 'hello world!');
8
9// 取消订阅
10PubSub.unsubscribe(token);
11. React代理配置
-
单个服务器代理
直接在package.json中加上
”proxy“:"http://localhost:xxxx"
-
多代理配置
在src下建立setupProxy.js文件
1// CommonJS语法 2const {createProxyMiddleware} = require('http-proxy-middleware'); 3 4module.exports = function(app){ 5 app.use( 6 createProxyMiddleware('/api1', { // 遇见前缀为 /api1 的请求, 就会触发该代理配置 7 target: 'http://localhost:5000', // 请求转发给谁 8 changeOrigin: true, // 控制服务器接受到的请求头中Host字段的值 为true时 服务器收到请求头为localhost:5000 // 为flase时 服务器收到请求头为localhost:3000 9 pathRewrite: {'^/api1':''} // 重写请求路径 10 }) 11 ), 12 app.use( 13 createProxyMiddleware('/api2', { 14 target: 'http://localhost:5001', 15 changeOrigin: true, 16 pathRewrite: {'^/api2':''} 17 }) 18 ) 19}
12. React路由
12.1. SPA概念
-
单页Web应用(single page web application, SPA)
-
整个应用只有一个完整的页面
-
点击页面中的链接浏览器不会刷新页面,只会做页面的局部更新
-
数据都需要通过ajax获取,并在前端异步展示
12.2. 路由概念
- 一个路由就是一个映射关系(key: value)
- key为路径, value可能是function或者component
路由分类:
-
后端路由
比如node中,value是function,用于处理客户端提交的请求
app.get(key, function(req, res){})
-
前端路由
浏览器路由,value是component,用于展示页面内容
12.3. react-router
react-router包括有Web,Native,Any
在这里学习使用web上的react路由使用,需要下载react-router-dom
-
最基本路由使用
<Link>
标签编写路由路径<Route>
标签注册路由组件最外部需要包一个
<BroserRouter>
或者<HashRouter>
,路由才会生效1export default class App extends Component { 2 render() { 3 return ( 4 <div> 5 <div className="row"> 6 <div className="col-xs-offset-2 col-xs-8"> 7 <div className="page-header"><h2>React Router Demo</h2></div> 8 </div> 9 </div> 10 <div className="row"> 11 <div className="col-xs-2 col-xs-offset-2"> 12 <div className="list-group"> 13 {/* 编写路由链接 */} 14 <Link className='list-group-item' to="/about">About</Link> 15 <Link className='list-group-item' to="/home">Home</Link> 16 </div> 17 </div> 18 <div className="col-xs-6"> 19 <div className="panel"> 20 <div className="panel-body"> 21 {/* 注册路由 */} 22 <Route path="/about" component={About}/> 23 <Route path="/home" component={Home}/> 24 </div> 25 </div> 26 </div> 27 </div> 28 </div> 29 ) 30 } 31}
1const root = createRoot(document.getElementById('root')); 2root.render(<BrowserRouter><App></App></BrowserRouter>);
-
NavLink使用
<NavLink>
可以看作是<Link>
的升级版,在路由被激活时,会追加一个激活后的样式,默认追加类名为active
1export default class App extends Component { 2 render() { 3 return ( 4 <div> 5 <Header/> 6 <div className="row"> 7 <div className="col-xs-2 col-xs-offset-2"> 8 <div className="list-group"> 9 {/* 编写路由链接 */} 10 <NavLink activeClassName='active' className='list-group-item' to="/about">About</NavLink> 11 <NavLink activeClassName='active' className='list-group-item' to="/home">Home</NavLink> 12 </div> 13 </div> 14 <div className="col-xs-6"> 15 <div className="panel"> 16 <div className="panel-body"> 17 {/* 注册路由 */} 18 <Route path="/about" component={About}/> 19 <Route path="/home" component={Home}/> 20 </div> 21 </div> 22 </div> 23 </div> 24 </div> 25 ) 26 } 27}
-
封装NavLink组件
原生NavLink组件每一次编写都要指定activeClassName,className等属性,造成代码冗余,因此需要对其进行封装,每次使用只需要指定to属性即可。
注意: React组件标签间值会自动作为key为
children
的属性传入组件props内1export default class App extends Component { 2 render() { 3 return ( 4 <div> 5 <Header/> 6 <div className="row"> 7 <div className="col-xs-2 col-xs-offset-2"> 8 <div className="list-group"> 9 <MyNavLink to="/about">About</MyNavLink> 10 <MyNavLink to="/home">Home</MyNavLink> 11 </div> 12 </div> 13 <div className="col-xs-6"> 14 <div className="panel"> 15 <div className="panel-body"> 16 {/* 注册路由 */} 17 <Route path="/about" component={About}/> 18 <Route path="/home" component={Home}/> 19 </div> 20 </div> 21 </div> 22 </div> 23 </div> 24 ) 25 } 26}
封装NavLink:
1export default class MyNavLink extends Component { 2 render() { 3 return ( 4 <NavLink activeClassName='active' className='list-group-item' {...this.props}></NavLink> 5 ) 6 } 7}
-
Switch使用
如果一个路由路径指定了多个组件,则多个组件会一起展示
用Switch标签包裹后即可实现单一匹配
1<div className="panel-body"> 2 {/* 注册路由 */} 3 <Switch> 4 <Route path="/about" component={About}/> 5 <Route path="/home" component={Home}/> 6 <Route path="/home" component={Test}/> 7 </Switch> 8</div>
-
多级路由路径下刷新浏览器样式丢失问题
1// 1. 采用public绝对路径 2<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css"> 3 4// 2.相对路径不要带. 5<link rel="stylesheet" href="/css/bootstrap.css"> 6 7// 3.采用HashRouter 8const root = createRoot(document.getElementById('root')); 9root.render(<HashRouter><App></App></HashRouter>);
-
路由的模糊匹配与严格匹配
默认模糊匹配
开启严格匹配:
严格匹配不建议随意开启,有时会导致无法继续二级路由
1export default class App extends Component { 2 render() { 3 return ( 4 <div> 5 <Header/> 6 <div className="row"> 7 <div className="col-xs-2 col-xs-offset-2"> 8 <div className="list-group"> 9 <MyNavLink to="/about">About</MyNavLink> 10 <MyNavLink to="/home/a/b">Home</MyNavLink> 11 </div> 12 </div> 13 <div className="col-xs-6"> 14 <div className="panel"> 15 <div className="panel-body"> 16 {/* 注册路由 */} 17 <Switch> 18 <Route exact={true} path="/about" component={About}/> 19 {/* 不开启严格匹配则该路由对NavLink指定的路径将会生效 */} 20 <Route exact={true} path="/home" component={Home}/> 21 </Switch> 22 </div> 23 </div> 24 </div> 25 </div> 26 </div> 27 ) 28 } 29}
-
Redirect使用
Redirect组件用于重定向,一般用于页面打开时默认展示路由
当Route一个都没有匹配时,前往Redirect指定的路由路径
1export default class App extends Component { 2 render() { 3 return ( 4 <div> 5 <Header/> 6 <div className="row"> 7 <div className="col-xs-2 col-xs-offset-2"> 8 <div className="list-group"> 9 <MyNavLink to="/about">About</MyNavLink> 10 <MyNavLink to="/home">Home</MyNavLink> 11 </div> 12 </div> 13 <div className="col-xs-6"> 14 <div className="panel"> 15 <div className="panel-body"> 16 {/* 注册路由 */} 17 <Switch> 18 <Route path="/about" component={About}/> 19 <Route path="/home" component={Home}/> 20 <Redirect to="/About"></Redirect> 21 </Switch> 22 </div> 23 </div> 24 </div> 25 </div> 26 </div> 27 ) 28 } 29}
-
嵌套路由
注意不要随便开启严格模式
嵌套路由是按照路由注册顺序依次进行匹配的
-
向路由组件传递params参数
在定义路由时以模板字符串形式定义参数内容(value);
在注册路由时声明接收params参数,以key形式声明
参数将会保存在路由组件内props属性内
1export default class Message extends Component { 2 state = { 3 messageArr: [ 4 {id: '001', title: '消息1'}, 5 {id: '002', title: '消息2'}, 6 {id: '003', title: '消息3'}, 7 ] 8 } 9 render() { 10 return ( 11 <div> 12 <ul> 13 { 14 this.state.messageArr.map((message)=>{ 15 return ( 16 <li key={message.id}> 17 {/* 向路由组件传递params参数 */} 18 <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> 19 </li> 20 ) 21 }) 22 } 23 </ul> 24 <hr></hr> 25 {/* 声明接收params参数 */} 26 <Route path="/home/message/detail/:id/:title" component={Detail}></Route> 27 </div> 28 ) 29 } 30}
-
向路由组件传递search参数
以query的形式在路由路径内声明即可,
参数保存在路由组件props的location内
需要通过qs模块进行解析
1export default class Message extends Component { 2 state = { 3 messageArr: [ 4 {id: '001', title: '消息1'}, 5 {id: '002', title: '消息2'}, 6 {id: '003', title: '消息3'}, 7 ] 8 } 9 render() { 10 return ( 11 <div> 12 <ul> 13 { 14 this.state.messageArr.map((message)=>{ 15 return ( 16 <li key={message.id}> 17 {/* 向路由组件传递params参数 */} 18 {/* <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> */} 19 20 {/* 向路由组件传递search参数 */} 21 <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> 22 </li> 23 ) 24 }) 25 } 26 </ul> 27 <hr></hr> 28 {/* 声明接收params参数 */} 29 {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} 30 31 {/* search参数无需声明接收 */} 32 <Route path="/home/message/detail" component={Detail}></Route> 33 </div> 34 ) 35 } 36}
1export default class Detail extends Component { 2 render() { 3 const {id, title} = qs.parse(this.props.location.search.slice(1)); 4 return ( 5 <div> 6 <ul> 7 <li>{id}</li> 8 <li>{title}</li> 9 <li>Message</li> 10 </ul> 11 </div> 12 ) 13 } 14}
-
向路由组件传递state参数
以对象形式定义
<Link>
标签内的to属性,声明pathname以及state在路由组件内通过location属性内的state属性获取
参数不会在浏览器地址栏上显示,但是刷新可以正常显示,因为location也是作为hostory属性的一部分
1export default class Message extends Component { 2 state = { 3 messageArr: [ 4 {id: '001', title: '消息1'}, 5 {id: '002', title: '消息2'}, 6 {id: '003', title: '消息3'}, 7 ] 8 } 9 render() { 10 return ( 11 <div> 12 <ul> 13 { 14 this.state.messageArr.map((message)=>{ 15 return ( 16 <li key={message.id}> 17 {/* 向路由组件传递params参数 */} 18 {/* <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> */} 19 20 {/* 向路由组件传递search参数 */} 21 {/* <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> */} 22 23 {/* 向路由组件传递state参数 */} 24 <Link to={{pathname: '/home/message/detail', state:{id:message.id, title:message.title}}}>{message.title}</Link> 25 </li> 26 ) 27 }) 28 } 29 </ul> 30 <hr></hr> 31 {/* 声明接收params参数 */} 32 {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} 33 34 {/* search参数无需声明接收 */} 35 <Route path="/home/message/detail" component={Detail}></Route> 36 </div> 37 ) 38 } 39}
-
push和replace模式
默认路由跳转是push模式,可以在
<Link>
标签内指定开启replace模式1<Link replace={true} to='/home/message/detail'>{message.title}</Link>
-
编程式路由导航
有时候需要在没有
<Link>
标签的情况下实现路由导航借助路由组件内
this.props.history
属性以编程方式进行路由的跳转1export default class Message extends Component { 2 state = { 3 messageArr: [ 4 {id: '001', title: '消息1'}, 5 {id: '002', title: '消息2'}, 6 {id: '003', title: '消息3'}, 7 ] 8 } 9 render() { 10 return ( 11 <div> 12 <ul> 13 { 14 this.state.messageArr.map((message)=>{ 15 return ( 16 <li key={message.id}> 17 {/* 向路由组件传递params参数 */} 18 <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> 19 <button onClick={()=>{this.pushShow(message.id, message.title)}}>push查看</button> 20 <button onClick={()=>{this.replaceShow(message.id, message.title)}}>replace查看</button> 21 22 {/* 向路由组件传递search参数 */} 23 {/* <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> */} 24 25 {/* 向路由组件传递state参数 */} 26 {/* <Link to={{pathname: '/home/message/detail', state:{id:message.id, title:message.title}}}>{message.title}</Link> */} 27 </li> 28 ) 29 }) 30 } 31 </ul> 32 <hr></hr> 33 {/* 声明接收params参数 */} 34 {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */} 35 36 {/* search参数无需声明接收 */} 37 <Route path="/home/message/detail" component={Detail}></Route> 38 39 <button onClick={this.back}>回退</button> 40 <button onClick={this.forward}>前进</button> 41 </div> 42 ) 43 } 44 45 back = ()=>{ 46 this.props.history.goBack(); 47 } 48 49 forward = ()=>{ 50 this.props.history.goForward(); 51 } 52 53 pushShow = (id, title)=>{ 54 // this.props.history.push(`/home/message/detail/${id}/${title}`); 55 // this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`); 56 this.props.history.push(`/home/message/detail`, {id, title}); 57 } 58 59 // 实现replace跳转到Detail组件 60 replaceShow = (id, title)=>{ 61 // this.props.history.replace(`/home/message/detail/${id}/${title}`); 62 // this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`); 63 this.props.history.replace(`/home/message/detail/`, {id, title}); 64 } 65}
-
withRouter使用
只有路由组件才有
this.props.history
属性对于一般组件如果想要实现路由跳转,需要使用
withRouter
函数进行跳转withRouter可以加工一般组件,让以班组间具有路由组件所特有的API,withRouter的返回值是一个新组件
1class Header extends Component { 2 render() { 3 console.log(this); 4 return ( 5 <div className="row"> 6 <div className="col-xs-offset-2 col-xs-8"> 7 <div className="page-header"><h2>React Router Demo</h2></div> 8 <button onClick={this.back}>回退</button> 9 <button onClick={this.forward}>前进</button> 10 </div> 11 </div> 12 ) 13 } 14 15 back = ()=>{ 16 this.props.history.goBack(); 17 } 18 19 forward = ()=>{ 20 this.props.history.goForward(); 21 } 22} 23 24export default withRouter(Header)
-
BrowserRouter 和 HashRouter的区别
-
底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本,
HashRouter使用的是URL的哈希值
-
url表现形式不一样
-
刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在hostory对象中
HashRouter刷新后会导致路由state参数丢失
-
12.4. 路由组件与一般组件的区别
- 路由组件会默认收到路由器传递的props:history,location,match
- 两者写法不同
13. redux
redux是一个专门用于做状态管理的JS库(不是react插件库)
作用:集中式管理react应用中多个组件共享的状态
13.1. redux三个核心概念
-
action
- 动作的对象
- 包含两个属性
- type:表示属性,值为字符串,唯一
- data:数据属性,可选
- 例子:{type: “ADD_STUDENT”, data:{name: “J”, age: 18}}
-
reducer
- 用于初始化状态,加工状态
- 加工时,根据旧的state和action,产生新的state的纯函数
-
store
将state,action,reducer联系在一起的对象
13.2. redux核心API
- createstore()
作用:创建包含指定reducer的store对象
- store对象
-
作用: redux库最核心的管理对象
-
它内部维护着:
-
state
-
reducer
-
-
核心方法:
-
getState()
-
dispatch(action)
-
subscribe(listener)
-
-
具体编码:
-
store.getState()
-
store.dispatch({type:‘INCREMENT’, number})
-
store.subscribe(render)
-
-
applyMiddleware()
作用:应用上基于redux的中间件(插件库)
-
combineReducers()
作用:合并多个reducer函数
14. react-redux
一个react的插件库,专门用于简化在react应用中使用redux
react-redux将所有组件分为两大类:
-
UI组件
只负责UI呈现
不使用redux的API
通过props接收数据
-
容器组件
负责管理数据和业务逻辑
使用redux的API
基本使用:
-
如何创建一个容器组件
靠react-redux的connect函数
mapStateToProps 返回store中的state对象传递给UI组件
mapDispatchToProps 返回操作状态的方法对象dispatch传递给UI组件调用
1// 引入Count的UI组件 2import CountUI from "../../components/Count"; 3// 引入connect用于连接UI组件和redux 4import { connect } from "react-redux"; 5import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from "../../redux/count_action"; 6 7// a函数的返回值作为状态props传递给CountUI组件 state ———— redux保存的状态值 8function mapStateToProps(state){ 9 return {count: state}; 10} 11 12function mapDispatchToProps(dispatch){ 13 return { 14 increment: (data)=>{ 15 dispatch(createIncrementAction(data)); 16 }, 17 decrement:(data)=>{ 18 dispatch(createDecrementAction(data)); 19 }, 20 asyncIncrement: (data, time)=>{ 21 dispatch(createIncrementAsyncAction(data, time)); 22 }} 23} 24 25const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI); 26 27export default CountContainer;
-
容器组件中的store是靠props传递进去的
1import React, { Component } from 'react' 2// import Count from './components/Count' 3import CountContainer from './containers/Count' 4import store from './redux/store' 5 6export default class App extends Component { 7 render() { 8 return ( 9 <div> 10 <CountContainer store={store}></CountContainer> 11 </div> 12 ) 13 } 14}
-
Provider批量传递store
当需要给多个容器组件传递store时,在外部套一个
<Provider>
标签,给它传递一个store属性即可对所有容器组件进行传递1const root = createRoot(document.getElementById('root')); 2root.render(<Provider store={store}><App></App></Provider>);
-
容器组件和UI组件可以整合成一个文件
-
combineReducer合并多个reducer
1// 引入createStore,专门用于创建redux中最为核心的store对象 2import { createStore, applyMiddleware, combineReducers } from "redux" 3// 引入为Count组件服务的reducer 4import countReducer from './reducers/count' 5import personReducer from "./reducers/person"; 6import thunk from "redux-thunk" 7 8// 保存的对象键值就是以后需要用到的状态对象名 9const allReducer = combineReducers( 10 { 11 count: countReducer, 12 persons: personReducer 13 } 14); 15 16const store = createStore(allReducer, applyMiddleware(thunk)); 17 18// 暴露一个全局唯一store对象 19export default store
-
redux-devtools-extension
redux开发者工具
1import {composeWithDevTools} from 'redux-devtools-extension' 2const store = createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
15. React扩展
15.1. setState
setState更新状态两种方法:
1 (1). setState(stateObj, [callback])------对象式的setState
2 1.stateObj为状态改变对象(该对象可以体现出状态的更改)
3 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
4
5 (2). setState(updater, [callback])------函数式的setState
6 1.updater为返回stateChange对象的函数。
7 2.updater可以接收到state和props。
8 4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
9总结:
10 1.对象式的setState是函数式的setState的简写方式(语法糖)
11 2.使用原则:
12 (1).如果新状态不依赖于原状态 ===> 使用对象方式
13 (2).如果新状态依赖于原状态 ===> 使用函数方式
14 (3).如果需要在setState()执行后获取最新的状态数据,
15 要在第二个callback函数中读取
15.2. lazyLoad
路由组件的lazyLoad
- 路由组件利用lazy函数动态加载
1const Home = lazy(()=>import('./Home'))
2const About = lazy(()=>import('./About'))
- 通过
<Suspense>
标签自定义一个Loading界面
1<div className="panel-body">
2 {/* 注册路由 */}
3 <Suspense fallback={<h1>Loading...</h1>}>
4 <Route path="/about" component={About}/>
5 <Route path="/home" component={Home}/>
6 </Suspense>
7</div>
15.2. Hooks
React Hooks可以让函数式组件使用state以及其他React特性
- 三个常用Hook
1(1). State Hook: React.useState()
2(2). Effect Hook: React.useEffect()
3(3). Ref Hook: React.useRef()
- State Hook
1(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
2(2). 语法: const [xxx, setXxx] = React.useState(initValue)
3(3). useState()说明:
4 参数: 第一次初始化指定的值在内部作缓存
5 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
6(4). setXxx()2种写法:
7 setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
8 setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
-
Effect Hook
Effect Hooks用于模拟类式组件中生命周期钩子
1(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
2(2). React中的副作用操作:
3 发ajax请求数据获取
4 设置订阅 / 启动定时器
5 手动更改真实DOM
6(3). 语法和说明:
7 useEffect(() => {
8 // 在此可以执行任何带副作用操作
9 return () => { // 在组件卸载前执行
10 // 在此做一些收尾工作, 比如清除定时器/取消订阅等
11 }
12 }, [stateValue]) // 监测stateValue改变则调用回调, 如果指定的是[], 回调函数只会在第一次render()后执行
13
14(4). 可以把 useEffect Hook 看做如下三个函数的组合
15 componentDidMount()
16 componentDidUpdate()
17 componentWillUnmount()
- Ref Hook
1(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
2(2). 语法: const refContainer = useRef()
3(3). 作用:保存标签对象,功能与React.createRef()一样
15.3. Fragment
用于忽略节点,最终不会在html中渲染
可以不用必须有一个真实的DOM根标签了
1<Fragment><Fragment>
2<></>
15.4. Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信(父子组件间props即可)
11) 创建Context容器对象:
2 const XxxContext = React.createContext()
3
42) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
5 <xxxContext.Provider value={数据}>
6 子组件
7 </xxxContext.Provider>
8
93) 后代组件读取数据:
10
11 //第一种方式:仅适用于类组件
12 static contextType = xxxContext // 声明接收context
13 this.context // 读取context中的value数据
14
15 //第二种方式: 函数组件与类组件都可以
16 <xxxContext.Consumer>
17 {
18 value => ( // value就是context中的value数据
19 要显示的内容
20 )
21 }
22 </xxxContext.Consumer>
1// 创建一个用于保存变量的Context对象
2const MyContext = React.createContext();
3
4export default class A extends Component {
5 state = {username: 'Tom', age:18}
6 render() {
7 const {username, age} = this.state;
8 return (
9 <div>
10 <h3>我是A组件</h3>
11 <h4>我的用户名是: {this.state.username}</h4>
12 <MyContext.Provider value={{username, age}}>
13 <B></B>
14 </MyContext.Provider>
15 </div>
16 )
17 }
18}
19
20class B extends Component {
21 render() {
22 return (
23 <div>
24 <h3>我是子组件</h3>
25 <h4>我从A组件收到的用户名是: </h4>
26 <C></C>
27 </div>
28 )
29 }
30}
31
32class C extends Component {
33 // 声明接收context
34 static contextType = MyContext;
35
36 render() {
37 console.log(this);
38 return (
39 <div>
40 <h3>我是孙组件</h3>
41 <h4>我从A组件收到的用户名是: {this.context.username}, 年龄是{this.context.age}</h4>
42 <h4>我从A组件收到的用户名是:
43 <MyContext.Consumer>
44 {value=>{return `${value.username},年龄是${value.age}`}}
45 </MyContext.Consumer>
46 </h4>
47 </div>
48 )
49 }
50}
15.5. 组件优化
Component的2个问题
-
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
-
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1:
重写shouldComponentUpdate()方法
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:
使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
** 注意: **
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
class Demo extends PurComponent{
render(){
return (...)
}
}
15.6. render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
使用children props: 通过组件标签体传入结构
使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到
render props
<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data}
15.7. Error boundary
理解:
错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面
特点:
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
1// 定义在父组件内
2// 生命周期函数,一旦后台组件报错,就会触发
3static getDerivedStateFromError(error) {
4 console.log(error);
5 // 在render之前触发
6 // 返回新的state
7 return {
8 hasError: true,
9 };
10}
11
12componentDidCatch(error, info) {
13 // 统计页面的错误。发送请求发送到后台去
14 console.log(error, info);
15}
15.8. 组件通信方式总结
组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
几种通信方式:
1.props:
(1).children props
(2).render props
2.消息订阅-发布:
pubs-sub、event等等
3.集中式管理:
redux、dva等等
4.conText:
生产者-消费者模式
比较好的搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
16. ReactRouter 6 使用
与ReactRouter5相比,改变了:
- 内置组件变化,移除了
<Switch/>
,新增了<Routes/>
;移除了<Redirect>
,新增<Navigate>
- 语法变化:
component={About}
变为element={<About/>}
- 新增多个hook:
useParams
、useNavigate
、useMatch
、useLocation
等 - 官方明确推荐函数式组件
16.1. NavLink使用以及重定向
1export default function App() {
2 return (
3 <div>
4 <div className="row">
5 <div className="col-xs-offset-2 col-xs-8">
6 <div className="page-header"><h2>React Router Demo</h2></div>
7 </div>
8 </div>
9 <div className="row">
10 <div className="col-xs-2 col-xs-offset-2">
11 <div className="list-group">
12 <NavLink className={(isActive)=>{ return isActive.isActive ? 'list-group-item active' : 'list-group-item' }} to="/home">Home</NavLink>
13 <NavLink className="list-group-item" to="/about">About</NavLink>
14 </div>
15 </div>
16 <div className="col-xs-6">
17 <div className="panel">
18 <div className="panel-body">
19 <Routes>
20 <Route path="/home" element={<Home></Home>}></Route>
21 <Route path="/about" element={<About></About>}></Route>
22 <Route path="/" element={<Navigate to="/about"></Navigate>}></Route>
23 </Routes>
24 </div>
25 </div>
26 </div>
27 </div>
28 </div>
29 )
30}
16.2. 路由表
- 建立一个单独文件用于存储路由表
1import About from "../pages/About";
2import Home from "../pages/Home";
3import { Navigate } from "react-router-dom";
4
5export default[
6 {
7 path: '/about',
8 element:<About></About>
9 },
10 {
11 path: '/home',
12 element:<Home></Home>
13 },
14 {
15 path: '/',
16 element:<Navigate to='/about'/>
17 }
18 ];
- 使用useRoutes创建路由表
1export default function App() {
2 const element = useRoutes(routes)
3 return (
4 <div>
5 <div className="row">
6 <div className="col-xs-offset-2 col-xs-8">
7 <div className="page-header"><h2>React Router Demo</h2></div>
8 </div>
9 </div>
10 <div className="row">
11 <div className="col-xs-2 col-xs-offset-2">
12 <div className="list-group">
13 <NavLink className={(isActive)=>{ return isActive.isActive ? 'list-group-item active' : 'list-group-item' }} to="/home">Home</NavLink>
14 <NavLink className="list-group-item" to="/about">About</NavLink>
15 </div>
16 </div>
17 <div className="col-xs-6">
18 <div className="panel">
19 <div className="panel-body">
20 {element}
21 </div>
22 </div>
23 </div>
24 </div>
25 </div>
26 )
27}
16.3. 嵌套路由
路由表
1export default[
2 {
3 path: '/about',
4 element:<About></About>
5 },
6 {
7 path: '/home',
8 element:<Home></Home>,
9 children: [
10 {
11 path:'news',
12 element: <News></News>
13 },
14 {
15 path:'message',
16 element: <Message></Message>
17 }
18 ]
19 },
20 {
21 path: '/',
22 element:<Navigate to='/about'/>
23 }
24 ];
二级组件展示
1export default function Home() {
2 return (
3 <div>
4 <h2>我是Home组件</h2>
5 <ul className="nav nav-tabs">
6 <li>
7 <NavLink className="list-group-item" to="news">News</NavLink>
8 </li>
9 <li>
10 <NavLink className="list-group-item" to="message">Message</NavLink>
11 </li>
12 </ul>
13 <Outlet></Outlet>
14 </div>
15 )
16}
16.4. 路由传参
Link指定路由路径传参
1export default function Message() {
2
3 const [messages] = useState([
4 {id: '001', title: '消息1', content: '111'},
5 {id: '002', title: '消息2', content: '222'},
6 {id: '003', title: '消息3', content: '333'},
7 {id: '004', title: '消息4', content: '444'},
8 ])
9 return (
10 <div>
11 <h2>Home组件内容</h2>
12 <div>
13 <div>
14 <ul>
15 {
16 messages.map((message)=>{
17 return (
18 <li key={message.id}>
19 {/* search */}
20 {/* <Link to={`detail?id=${message.id}&title=${message.title}&content=${message.content}`}>{message.title}</Link> */}
21 {/* state */}
22 <Link to='detail' state={{id: message.id, title: message.title, content: message.content}}>{message.title}</Link>
23 </li>
24 )
25 })
26 }
27 </ul>
28 </div>
29 <Outlet></Outlet>
30 </div>
31 </div>
32 )
33}
路由组件接收参数
1
2export default function Detail() {
3 // params传参
4 // const params = useParams();
5
6 // search传参
7 // const [searchParams, setSearch] = useSearchParams();
8
9 // state传参
10 const {id, title, content} = useLocation().state;
11
12 return (
13 <div>
14 <ul>
15 {/* params参数 */}
16 {/* <li>{params.id}</li>
17 <li>{params.title}</li>
18 <li>{params.content}</li> */}
19
20 {/* search参数 */}
21 {/* <li>{searchParams.get('id')}</li>
22 <li>{searchParams.get('title')}</li>
23 <li>{searchParams.get('content')}</li> */}
24
25 {/* state参数 */}
26 <li>{id}</li>
27 <li>{title}</li>
28 <li>{content}</li>
29 </ul>
30 </div>
31 )
32}
16.5. 编程式路由导航
利用useNavigate实现路由跳转
1export default function Message() {
2
3 const [messages] = useState([
4 {id: '001', title: '消息1', content: '111'},
5 {id: '002', title: '消息2', content: '222'},
6 {id: '003', title: '消息3', content: '333'},
7 {id: '004', title: '消息4', content: '444'},
8 ])
9
10 const navigate = useNavigate();
11 function showData(message){
12 navigate('detail', {
13 replace: false,
14 state: {
15 id: message.id,
16 title: message.title,
17 content: message.content
18 }
19 });
20 }
21
22 return (
23 <div>
24 <h2>Home组件内容</h2>
25 <div>
26 <div>
27 <ul>
28 {
29 messages.map((message)=>{
30 return (
31 <li key={message.id}>
32 {/* search */}
33 {/* <Link to={`detail?id=${message.id}&title=${message.title}&content=${message.content}`}>{message.title}</Link> */}
34 {/* state */}
35 <Link to='detail' state={{id: message.id, title: message.title, content: message.content}}>{message.title}</Link>
36 <button onClick={()=>{showData(message)}}>展示详情</button>
37 </li>
38 )
39 })
40 }
41 </ul>
42 </div>
43 <Outlet></Outlet>
44 </div>
45 </div>
46 )
47}
一般组件内实现路由前进后退
1export default function Header() {
2 const navigate = useNavigate();
3
4 function back(){
5 navigate(-1);
6 }
7
8 function forward(){
9 navigate(1);
10 }
11
12 return (
13 <div className="col-xs-offset-2 col-xs-8">
14 <div className="page-header"><h2>React Router Demo</h2>
15 </div>
16 <button onClick={back}>后退</button>
17 <button onClick={forward}>前进</button>
18 </div>
19 )
20}
16.6. useInRouterContext()
返回一个bool值,判断当前组件是否处于路由环境中
1var s = useInRouterContext()
16.7. useNavigationType()
返回当前导航类型(用户是如何来到当前页面的)
返回值:POP
、PUSH
、REPLACE
16.8. useOutlet()
用来呈现当前组件中渲染的嵌套路由
1const result = useOutlet();
2// 如果当前嵌套路由未挂载 则返回null
3// 否则返回当前挂载嵌套路由
- 原文作者:小小呆呆没有脑袋
- 原文链接:https://www.gujin.store/post/2022-4-1-reactlearn/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。