组件通信
父子组件之间通讯
- 父传子
父组件的代码
import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";
export default class App extends Component{
render(){
return(
<div>
<Sub title = "今年过节不收礼" />
</div>
)
}
}
子组件
import React from "react";
const Sub = (props) => {
return(
<h1>
{ props.title }
</h1>
)
}
export default Sub;
子组件向父组件通信
利用回调函数,可以实现子组件向父组件通信:父组件将一个函数作为 props 传递给子组件,子组件调用该回调函数,便可以向父组件通信。
SubComponent.js:
import React from "react";
const Sub = (props) => {
const cb = (msg) => {
return () => {
props.callback(msg)
}
}
return(
<div>
<button onClick = { cb("我们通信把") }>点击我</button>
</div>
)
}
export default Sub;
APP.js:
import React,{ Component } from "react";
import Sub from "./SubComponent.js";
import "./App.css";
export default class App extends Component{
callback(msg){
console.log(msg);
}
render(){
return(
<div>
<Sub callback = { this.callback.bind(this) } />
</div>
)
}
}
跨级组件通信 祖向后代传
采用下面两种方式:
- 中间组件层层传递 props
- 使用 context 对象
对于第一种方式,如果父组件结构较深,那么中间的每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是这些中间组件自己所需要的。不过这种方式也是可行的,当组件层次在三层以内可以采用这种方式,当组件嵌套过深时,采用这种方式就需要斟酌了。
使用 context 是另一种可行的方式,context 相当于一个全局变量,是一个大容器,我们可以把要通信的内容放在这个容器中,这样一来,不管嵌套有多深,都可以随意取用。
使用 context 也很简单,需要满足两个条件:
- 上级组件要声明自己支持 context,并提供一个函数来返回相应的 context 对象
- 子组件要声明自己需要使用 context
1)创建context容器对象:
const xxxContext = React.createcontext()
const {Provider}= xxxContext
2)渲染子组时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:
<Provider value={数据}>
子组件
</Provider>
3)后代组件读取数据:
//第一种方式:仅适用于类组件
static contextType = xxxContext //声明接收contextthis.context
//读取context中的value数据
//第二种方式:函崴数组件与类组件都可以
<XxxContext.Consumer> { value => (
// value就是context中的value数据要显示的内容 )
} </xxxContext.Consumer>
非嵌套组件间通信
兄弟节点的共同⽗节点,由⽗节点转发信息,实现兄弟间通信。
消息订阅与发布 pubsub
可以借助 Redux
或 Mobx
等全局状态管理⼯具进⾏通信,它们会维护⼀个全局状态中⼼(Store),并可以根据不同的事件产⽣新的状态。
router
路由的基本使用
路由分为两种 BrowserRouter 与 HashRouter
1.明确好界面中的导航区、展示区
2.导航区的a标签改为Link标签
<Link to=”/xxxxx" >Demo</Link>
3.展示区写Route标签进行路径的匹配
<Route path= ' /xxxx' component={Demo}/>
4.的最外侧包裹了一个<BrowserRouter>或<HashRouter>
案例 首先创建两个组件,然后在App.js文件中应用并使用, 接着在到index.js文件中注册App组件,最后用index.html文件渲染到页面上
App,js文件
import React, { Component } from 'react';
import { Link,Route } from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html 要靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件 */}
<Link className="list-group-item" to="/home">Home</Link>
<Link className="list-group-item" to="/about">About</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/home" component={Home}/>
<Route path="/about" component={About}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
组件一:Home
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<h3>我是Home的内容</h3>
)
}
}
组件一:About
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是About的内容</h3>
)
}
}
index.js文件
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import {BrowserRouter} from 'react-router-dom'
ReactDOM.render(
<BrowserRouter> // <App>的最外侧包襄了一个<BrowserRouter>或<HashRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
)
路由的扩展使用
NavLink与封装NavLink
1.NavLink 可以实现路由链接的高亮,通过activeclassName指定样式名
2.标签体内容是一个特殊的标签属性
3.通过this.props.children可以获取标签体内容
App.js文件修改的代码
{/* 在React中靠路由链接实现切换组件 */}
<NavLink activeClassName="add" className="list-group-item" to="/home">Home</NavLink>
<NavLink activeClassName="add" className="list-group-item" to="/about">About</NavLink>
这里用 activeClassName="add" 来控制按钮高亮的颜色显示
渲染文件 index.html 文件修改后的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./css/bootstrap.css">
<style>
.add{
background-color: rgb(209,137,4) !important; // 因为 bootstrap 的权重比较高,所以要用!important
color:white !important;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
</html>
NavLink 比 Link 好用
Switch的使用
1.通常情况下,path和lcomponent是一一对应的关系。
2.Switch可以提高路由匹配效率(单一匹配)。
import React, { Component } from 'react'
import {Route,Switch} from 'react-router-dom'
import Home from './pages/Home' //Home是路由组件
import About from './pages/About' //About是路由组件
import Header from './components/Header' //Header是一般组件
import MyNavLink from './components/MyNavLink'
import Test from './pages/Test1'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<Header/>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 原生html中,靠<a>跳转不同的页面 */}
{/* <a className="list-group-item" href="./about.html">About</a>
<a className="list-group-item active" href="./home.html">Home</a> */}
{/* 在React中靠路由链接实现切换组件--编写路由链接 */}
{/* <NavLink activeClassName="atguigu" className="list-group-item" to="/about">About</NavLink>
<NavLink activeClassName="atguigu" className="list-group-item" to="/home">Home</NavLink> */}
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/Home">Home</MyNavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="abc" component={ABC} />
<Route path="/Home" component={Test}/>
</Switch>
</div>
</div>
</div>
</div>
</div>
)
}
}
上述代码使用封装好的MyNavLink分别指向了
/about
/home
然后使用Route来设置对应的地址展示的路由组件
Switch作用体现:
由于路由的默认是多次匹配(既:通过for循环遍历一遍每个路由组件,找到所有对应该路径的路由组件来展,如果路由组件很多的话造成了效率过低)
这里用Switch包裹Route来实现单一匹配,既一个路由对应第一个该路由的路由组件。原理为遍历到该路由组件后,停止遍历。
解决多级路径刷新页面样式丢失的问题
1.public/index.htm1 中引入样式时不写﹒/ 写/(常用)
2.public/index.htm1 中引入样式时不写﹒/写%PUBLIC_URL%(常用)
3.使用HashRouter 要少用
路由的严格匹配与模糊匹配
1.默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
2.开启严格匹配:
3.严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
注册路由 默认模糊匹配 添加exact为精确匹配看需要使用
Redirect的使用
1.一股写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路山
2.具体编码:
<Switch>
<Route path=" / about" component={About}/>
<Route path=" / home" component={Home}/><Redirect to=" / about" />
</ Switch>
嵌套路由
1.注册子路由时要写上父路由的path值
2.路由的匹配是按照注册路由的顺序进行的
向路由组件传递参数
1.params参数
路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>
注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>
接收参数:this.props.match.params
2.search参数
路由链接(携带参数):<Link to='/demo/test?name=tom&age=18'}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
3.state参数
路由链接(携带参数):<Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
接收参数:this.props.location.state
备注:刷新也可以保留住参数
编程式路由跳转
push与repalce
push跳转会留下痕迹可以后退 但repalce不会
借助this.prosp.history对象上的API对操作路由跳转、前进、后退
this.prosp.history.push()
this.prosp.history.replace()
this.prosp.history.goBack()
this.prosp.history.goForward()
this.prosp.history.go()
withRouter 的使用
withRouter 使 非路由组件拥有路由组件所特有的特性
withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
withRouter的返回值是一个新组件
Header 组件代码
import React, { Component } from 'react'
import {withRouter} from 'react-router-dom'
class Header extends Component {
// 回退
back = () => {
this.props.history.goBack()
}
// 前进
forward = () => {
this.props.history.goForward()
}
/// go
go = () => {
this.props.history.go(2)
}
render() {
// console.log('一般组件',this.props)
return (
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.back}>回退</button>
<button onClick={this.forward}>前进</button>
<button onClick={this.go}>go</button>
</div>
)
}
}
export default withRouter(Header)
BrowserRouter与HashRouter的区别
1.底层原理不一样:
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
HashRouter使用的是URL的哈希值。
2.path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
3.刷新后对路由state参数的影响
1).BrowserRouter没有任何影响,因为state保存在history对象中。
2).HashRouter刷新后会导致路由state参数的丢失!!!
4.备注:HashRouter可以用于解决一些路径错误相关的问题。
redux
Redux设计和使用的三项原则:
store是唯一的、
只有store能够改变自己的内容、
Reducer必须是纯函数。
纯函数指的是,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用。
redux流程原理
在 React 中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 id 和负载 (payload) 的 action。action 中的 payload 是可选的,action 将其转发给 Reducer。
当 reducer 收到 action 时,通过 switch...case 语法比较 action 中 type。 匹配时,更新对应的内容返回新的 state。
当 Redux 状态更改时,连接到 Redux 的组件将接收新的状态作为 props。当组件接收到这些 props 时,它将进入更新阶段并重新渲染 UI。
reducer可以接受state,但是绝不能修改state。
配置与使用Redux
配置Redux开发环境的最快方法是使用create-react-app工具。在开始之前,确保已经安装并更新了nodejs,npm。
npm install redux
创建store目录,在store目录下新建index.js文件,键入以下内容:
/*
该文件专门用于暴露一个store对象,整个应用只有一个store对象
/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore} from 'redux'
//引入为Count组件服务的reducer
import reducer from './reducer';//暴露store
export default createStore(reducer)
在store目录下创建reducer.js文件,键入以下内容:
const defaultState = 0//reducer可以接收state,但是不能修改state
export default (state = defaultState,action) => {
const { type, data } = action;
switch (type) {case "increment":
return data + state;
default:
return state;}}
在组件中就可以使用store的数据
import React, { Component } from "react";
import store from "../store/index";
import "./index.css";
export default class index extends Component {
getIncrement = () => {
let val = this.getInpVal.valuestore.dispatch(
{type: "increment",
data: val*1,
});
};
getDecrement = () => {
let val = this.getInpVal.value
store.dispatch({
type: "decrement",
data: val*1,
});
};
getIncrementOdd = () => {
if (store.getState()%2!=0) {
let val = this.getInpVal.valuestore.dispatch(
{type: "increment",
data: val1,
});
}
};
getIncrementOAsync = () => {
setTimeout(() => {
let val = this.getInpVal.value
store.dispatch({
type: "increment",
data: val*1,
});
}, 1000);
};
render() {
return (
<div>
<h3>累加和为:{store.getState()}</h3>
<select ref={c => this.getInpVal = c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.getIncrement}>+</button>
<button onClick={this.getDecrement}>-</button>
<button onClick={this.getIncrementOdd}>奇数加</button>
<button onClick={this.getIncrementOAsync}>异步加</button>
</div>
);
}
}
扩展
setState 扩展
(1). setstate(statechange,[callback]) ------对象式的setstate
1.statechange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数,它在状态更新完毕、界面也更新后(render调用后)才被调用,可以拿到最新值
(2). setstate(updater ,[callback])------函数式的setstate
1.updater为返回statechange对象的函|数。
2.updater可以接收到state和props。
4.callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用。
总结:
1.对象式的setstate是函数数式的setstate的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态===>使用对象方式
(2).如果新状态依赖于原状态===>使用函数方式
(3).如果需要在setState(O)执行后获取最新的状态数据,
要在第二个callback函数中读取
懒加载 lzayload
通常,使用静态导入声明组件:
import MarkdownPreview from './MarkdownPreview.js';
若要延迟加载此组件的代码,直到首次呈现,请将此导入替换为:
import { lazy } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
现在,组件的代码已按需加载,您还需要指定加载时应显示的内容。您可以通过将惰性组件或其任何父组件包装到边界中来执行此操作:
<Suspense fallback={<Loading />}> //未加载出的时候的显示
<h2>Preview</h2>
<MarkdownPreview />
</Suspense>
Hooks
useState
useState 用于在函数组件中调用给组件添加一些内部状态 state,正常情况下纯函数不能存在状态副作用,通过调用该 Hook 函数可以给函数组件注入状态 state
useState 唯一的参数就是初始 state,会返回当前状态和一个状态更新函数,并且 useState 返回的状态更新函数不会把新的 state 和旧的 state 进行合并,如需合并可使用 ES6 的对象结构语法进行手动合并
const [state, setState] = useState(initialState);
等价 class 示例
useState 返回的状态类似于 class 组件在构造函数中定义 this.state,返回的状态更新函数类似于 class 组件的 this.setState
import React from 'react';
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<button onClick={() => this.setState({ count: this.state.count - 1 })}>-</button>
<input type="text" value={this.state.count} onChange={(e) => this.setState({ count: e.target.value })} />
<button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
</div>
);
}
}
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,可以往 setState 传递函数,该函数将接收先前的 state,并返回一个更新后的值
import React, { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0);
const lazyAdd = () => {
setTimeout(() => {
// 每次执行都会最新的state,而不是使用事件触发时的state
setCount(count => count + 1);
}, 3000);
}
return (
<div>
<p>the count now is {count}</p>
<button onClick={() => setCount(count + 1)}>add</button>
<button onClick={lazyAdd}>lazyAdd</button>
</div>
);
}
惰性初始 state
如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只会在初始渲染时被调用
import React, { useState } from 'react'
export default function Counter(props) {
// 函数只在初始渲染时执行一次,组件重新渲染时该函数不会重新执行
const initCounter = () => {
console.log('initCounter');
return { number: props.number };
};
const [counter, setCounter] = useState(initCounter);
return (
<div>
<button onClick={() => setCounter({ number: counter.number - 1 })}>-</button>
<input type="text" value={counter.number} onChange={(e) => setCounter({ number: e.target.value})} />
<button onClick={() => setCounter({ number: counter.number + 1 })}>+</button>
</div>
);
}
useEffect
在函数组件主体内(React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
useEffect Hook 的使用则是用于完成此类副作用操作。useEffect 接收一个包含命令式、且可能有副作用代码的函数
useEffect函数会在浏览器完成布局和绘制之后,下一次重新渲染之前执行,保证不会阻塞浏览器对屏幕的更新
import React, { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
// useEffect 内的回调函数会在初次渲染后和更新完成后执行
// 相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>count now is {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
等价 class 示例
import React from 'react';
export default class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
render() {
return (
<div>
<p>count now is {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
</div>
);
}
}
清除 effect
通常情况下,组件卸载时需要清除 effect 创建的副作用操作,useEffect Hook 函数可以返回一个清除函数,清除函数会在组件卸载前执行。组件在多次渲染中都会在执行下一个 effect 之前,执行该函数进行清除上一个 effect
清除函数的执行时机类似于 class 组件componentDidUnmount 生命周期,这的话使用 useEffect 函数可以将组件中互相关联的部分拆分成更小的函数,防止遗忘导致不必要的内存泄漏
import React, { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('start an interval timer')
const timer = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
// 返回一个清除函数,在组件卸载前和下一个effect执行前执行
return () => {
console.log('destroy effect');
clearInterval(timer);
};
}, []);
return (
<div>
<p>count now is {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
优化 effect 执行
默认情况下,effect 会在每一次组件渲染完成后执行。useEffect 可以接收第二个参数,它是 effect 所依赖的值数组,这样就只有当数组值发生变化才会重新创建订阅。但需要注意的是:
- 确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量
- 传递一个空数组作为第二个参数可以使 effect 只会在初始渲染完成后执行一次
import React, { useState, useEffect } from 'react'; export default function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 仅在 count 更改时更新 return ( <div> <p>count now is {count}</p> <button onClick={() => setCount(count + 1)}>+</button> </div> ); }
useContext
Context 提供了一个无需为每层组件手动添加 props ,就能在组件树间进行数据传递的方法,useContext 用于函数组件中订阅上层 context 的变更,可以获取上层 context 传递的 value prop 值
useContext 接收一个 context 对象(React.createContext的返回值)并返回 context 的当前值,当前的 context 值由上层组件中距离当前组件最近的 的 value prop 决定
import React, { useContext, useState } from 'react';
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 为当前 theme 创建一个 context
const ThemeContext = React.createContext();
export default function Toolbar(props) {
const [theme, setTheme] = useState(themes.dark);
const toggleTheme = () => {
setTheme(currentTheme => (
currentTheme === themes.dark
? themes.light
: themes.dark
));
};
return (
// 使用 Provider 将当前 props.value 传递给内部组件
<ThemeContext.Provider value={{theme, toggleTheme}}>
<ThemeButton />
</ThemeContext.Provider>
);
}
function ThemeButton() {
// 通过 useContext 获取当前 context 值
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button style={{background: theme.background, color: theme.foreground }} onClick={toggleTheme}>
Change the button's theme
</button>
);
}
useReducer
useReducer 作为 useState 的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。
使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为父组件可以向自组件传递 dispatch 而不是回调函数
import React, { useReducer } from 'react'
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}