(1)React是什么
react是用于构建用户 界面(视图) 的JavaScript库
也可以说react是一个将数据渲染为html视图开端的开源JS库
(2)React的功能是什么
操作Dom呈现页面
(3)为什么学React:原生JS的痛点
1 原生js操作dom效率低,繁琐
document.getElamentById('name')
2 使用js直接操作dom,浏览器会进行大量的重汇重排
3.原生js没有组件化的编码方案,代码复用率低
组件化:html、css、js都拆
(4)React的特点
1.采用组件化的模式,声明式编码,提高开发效率及组件的复用率
2.在React Native中可以使用React语法进行移动端开发
3.使用虚拟DOM+优秀的Diffing算法,尽量少的与真实的DOM的交互
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>Documenttitle>
head>
<body>
<div id="test">div>
<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">
//1.创建虚拟dom
const Vdom=<h1>hello-react</h1>
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
script>
body>
html>
//创建内容展示:
<h1 id="title">
<span id="thespan">hello-reactspan>
h1>
(1)使用jsx创建虚拟dom
<script type="text/babel">
//1.创建虚拟dom
const Vdom=<h1 id="title"><span id="thespan">hello-react</span></h1>
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
script>
(2)使用js创建虚拟dom
api:React.createElement(标签名,标签属性,标签内容):用于创建虚拟dom
<script type="text/javascript">
// 创建虚拟dom React.createElement(标签名,标签属性,标签内容)
const Vdom=React.createElement('h1',{id:'title'},React.createElement('span',{id:'thespan'},'hello-react'))
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
script>
(3)结论
jsx是js在react方面的语法糖
它其实是一个Object对象
关于虚拟dom
<script type="text/babel">
//1.创建虚拟dom
const Vdom=<h1><span>hello-react</span></h1>
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
//创建真实际dom
const Tdom=document.getElementById('test')
console.log('虚拟dom',Vdom)
console.log('真实dom',Tdom)
script>
XML 早期用于存储和传输数据
< student >
< name> 18< /name >
student >
注意区分:js表达式和js语句
- 表达式:表达式产生一个值可以放在任何一个需要值的地方。
例如:a,a+b,add(1),arr.map(),function(){}- 语句: 例如:if(){},for(){},switch(){}
<script type="text/babel">
const myId="guigu"
const myName="lihua"
//1.创建虚拟dom
const Vdom2=(
<h2 id="{myId}">
<span>{myName}</span>
</h2>
)
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom2,document.getElementById('test'))
</script>
const Vdom2=(
<h2 id="{myId}" className="title">
<span>{myName}</span>
</h2>
)
const Vdom2=(
<h2 id="{myId}" className="title">
<span style={{color:'white',fontSize:'40px'}}>{myName}</span>
</h2>
)
<input type="text"/>
<input type="text"></input>
{/*{this.input1=currentNode;console.log(currentNode)}}/> */}
<script type="text/babel">
// 模拟数据
const title="前端框架列表"
const data=['angular','vue','react']
//1.创建虚拟dom
const Vdom=(
<div>
<h2>{title}</h2>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
//2.渲染虚拟dom到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(Vdom,document.getElementById('test'))
(1)模块:
(2)组件:
名称 | 描述 |
---|---|
函数式组件 | 用函数定义的组件,适用于简单组件的定义 |
类式组件 | 用类定义的组件,适用于复杂组件的定义 |
<script type="text/babel">
//1.创建函数式组件
function Demo(){
//此处的this是undefined 因为bable翻译后开启了严格模式
return <h2>我是函数定义的简单组件,适用于简单组件的定义</h2>
}
//2.渲染组件到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
执行ReactDOM.render(
1.react解析了组件标签,找到了Demo组件
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟dom转化为真实dom
3.将dom呈现在页面中。
<script type="text/babel">
//1.创建类式组件 必须继承react中内置的类 React.Component
class MyComponent extends React.Component{
render(){
return <h2>我是定义的类式组件</h2>
}
}
//2.渲染组件到页面 ReactDom.render(虚拟DOM,容器)
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
ReactDOM.render(< MyComponent/>,document.getElementById(‘xxxx’))之后发生了什么?
1.MyComponent解析了组件标签,找到了MyComponent组件
2.发现组件是类定义的,随后new出来该类的实例,并通过该实例调用到原型上的rander方法
3.将rander返回的虚拟dom转化为真实dom
4.将dom渲染在页面中
名称 | 描述 |
---|---|
简单组件 | 没有state的 |
复杂组件 | 有state的 |
(1)实例
script type="text/babel">
class Weather extends React.Component{
constructor(props){
super(props)
// 初始化状态
this.state={isHot:true}
// 将原型对象上的方法转为实例自身上的方法
this.changeWeather=this.changeWeather.bind(this)
}
changeWeather(){
//1.类中的方法默认开启严格模式
//2. 由于changeWeather是作为onclick事件的回调 不是通过实例调用的而是直接调用
//结论:changeWeather中的this是undefined
// 严重注意:状态不可直接跟改 要使用内置的api
this.setState({isHot:!this.state.isHot})
}
render(){
return <h2 id='title' onClick={this.changeWeather}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h2>
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
(2)使用this.setState更新state
这里的更新是一种合并不会影响其他属性,而不是覆盖
this.setState({isHot:!this.state.isHot})
(3)state的简写方式
主要运用原理:类中可以直接写赋值语句,含义是给car的实例对象添加一个属性。
<script type="text/babel">
class Weather extends React.Component{
constructor(props){
super(props)
}
// 类中可以直接写赋值语句 含义是给类的实例添加一个属性
state={isHot:true}
changeWeather=()=>{
this.setState({isHot:!this.state.isHot})
}
render(){
return <h2 id='title' onClick={this.changeWeather}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h2>
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
(4)总结state
组件中rander方法中的this为组件的实例对象
组件自定义的方法中this为undefined,如何解决?
(1)使用bind强制改变指向
(2)使用箭头函数
状态数据不能直接修改,要用this.setState({})
(1)基本用法
<script type="text/babel">
class Person extends React.Component{
constructor(props){
super(props)
}
render(){
console.log(this)
return(
<ul>
<li>姓名{this.props.name}</li>
<li>年龄{this.props.age}</li>
<li>性别{this.props.sex}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="tom" age="18" sex="man"/>,document.getElementById('test'))
</script>
(2)对标签属性进行限制和设置默认值
<!-- 引入prop-types用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component{
constructor(props){
super(props)
}
render(){
const {name,age,sex} = this.props
return(
<ul>
<li>姓名{name}</li>
<li>年龄{age}</li>
<li>性别{sex}</li>
</ul>
)
}
}
// 指定参数类型
Person.propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
fun:PropTypes.func,
}
// 指定默认值
Person.defaultProps={
sex:'男',
age:18
}
const p={name:'tom',age:"18",sex:'man'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
//会出现报错信息:Warning: Failed prop type: Invalid prop `age` of type `string` supplied to `Person`, expected `number`.in Person
(3)props值是只读的
render(){
this.props.age=19 //报错
const {name,age,sex} = this.props
return(
<ul>
<li>姓名{name}</li>
<li>年龄{age}</li>
<li>性别{sex}</li>
</ul>
)
}
(4)props简写形式
将propTypes和defaultProps放在类定义的内部
<script type="text/babel">
// 创建组件
class Person extends React.Component{
// 指定类型
static propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
fun:PropTypes.func,
}
// 设置默认值
static defaultProps={
sex:'男',
age:18
}
state={secname:'lihua'}
render(){
const {name,age,sex} = this.props
return(
<ul>
<li>姓名{name}</li>
<li>年龄{age}</li>
<li>性别{sex}</li>
<li>state:{this.state.secname}</li>
</ul>
)
}
}
const p={name:'tom',age:18,sex:'man'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'))
</script>
(5)函数式组件中使用props
<script type="text/babel">
//1.创建函数式组件
function MyComponent(props){
return <h2 className='title'>我是{props.name}</h2>
}
//2.渲染组件到页面 ReactDom.render(虚拟DOM,容器)
const p={name:'tom',age:18,sex:'man'}
ReactDOM.render(<MyComponent {...p}/>,document.getElementById('test'))
</script>
类中的构造器是否接收props,是否传递给super取决于:是否希望在构造器中通过this访问props
构造器可以直接删除
constructor(props){
super(props)
}
(1)字符串形式的ref
不推荐
因为string类型的ref存在效率的问题。
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示输入框的数据
showData = ()=>{
alert(this.refs.input1.value)
}
showData2 = ()=>{
alert(this.refs.input2.value)
}
render(){
return (
<div>
<input type="text" placeholder="点击按钮提示数据" ref="input1"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} ref="input2"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
(2)回调函数形式的ref
<script type="text/babel">
//创建组件
class Demo extends React.Component{
//展示输入框的数据
showData = ()=>{
alert(this.input1.value)
}
showData2 = ()=>{
alert(this.input2.value)
}
render(){
return (
<div>
<input type="text" placeholder="点击按钮提示数据" ref={currentNode=>this.input1=currentNode}/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input type="text" placeholder="失去焦点提示数据" onBlur={this.showData2} ref={currentNode=>this.input2=currentNode}/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
(3)createRef API形式的ref
React.createRef:调用后可以返回一个容器,该容器可以存储被Ref所标识的节点
该容器是专人专用的
<script type="text/babel">
//创建组件
class Demo extends React.Component{
input1 = React.createRef()
input2 = React.createRef()
showData = ()=>{
alert(this.input1.current.value)
}
showData2 = ()=>{
alert(this.input2.current.value)
}
render(){
return (
<div>
<input type="text" placeholder="点击按钮提示数据" ref={this.input1 }/>
<input type="text" placeholder="点击按钮提示数据" ref={this.input2 }/>
<button onClick={this.showData}>点我提示左侧数据</button>
<button onClick={this.showData2}>点我提示左侧数据</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
1.通过onXxx属性指定事件处理函数(注意大小写)
(1)React使用的是自定义事件,而不是原生dom事件------为了更好的兼容性
(2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素----------为了高效
2. 通过event.target得到发生事件的dom元素
3.不要过度的使用ref
<script type="text/babel">
//创建组件
class Demo extends React.Component{
showData2 = (event)=>{
alert(event.target.value)
}
render(){
return (
<div>
<input type="text" placeholder="失去焦点提示数据" onBlur={this.showData2}/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo/>,document.getElementById('test'))
</script>
页面中输入类的dom 是现用现取的状态 就是非受控组件
<script type="text/babel">
//创建组件
class Login extends React.Component{
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
let {username,password} = this
alert(`你输入的用户名是${username.value},你输入的密码是${password.value}`)
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" ref={c=>this.username=c}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" ref={c=>this.password=c}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
受控组件:页面中所有输入类的dom随着输入,输入的值被维护在状态【state】中。
<script type="text/babel">
//创建组件
class Login extends React.Component{
state={username:'',password:''}
// 提交
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
alert(`你输入的用户名是${this.state.username},你输入的密码是${this.state.password}`)
}
// 保存用户名在状态中
saveUsername=(event)=>{
this.setState({username:event.target.value})
}
// 保存密码在状态中
savePassword=(event)=>{
this.setState({password:event.target.value})
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" onChange={this.saveUsername}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" onChange={this.savePassword}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
高阶函数: 如果一个函数符合下面两个规范中的任何一个,那这个函数就是高阶函数。
(1)若a函数,接收的参数是一个函数,那么a函数就可以称为高阶函数
(2)若a函数调用的返回值为一个函数,那么a就可以称为高阶函数
常见的高阶函数 promise、setTimeOut、arr.map…
函数柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function add(a){
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
let a = add(1)(2)(3)
console.log(a)
<script type="text/babel">
//创建组件
class Login extends React.Component{
state={username:'',password:''}
// 提交
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
alert(`你输入的用户名是${this.state.username},你输入的密码是${this.state.password}`)
}
// 保存表单数据到状态中
saveFomeData=(type)=>{
return (event)=>{
// 使用[]来获取变量 因为直接写type会把type当做字符串而不是变量
this.setState({[type]:event.target.value})
}
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" onChange={this.saveFomeData('username')}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" onChange={this.saveFomeData('password')}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
saveUserinfo=(type)=>{
return (event)=>{
this.setState({[type]:event.target.value})
}
}
注意:这里为什么是return这个函数
是因为调用的时候onChange={this.saveFomeData('password')}的涵义是
将this.saveFomeData('password')的返回值作为onChange的回调。
注意:这里的[type]:event.target.value属性名要用[]来包裹(用于读取变量),否则会被视为普通字符串。
不用柯里化
onChange={(event)=>this.saveFomeData(‘username’,event)}
onchange需要函数作为回调,就利用箭头函数来传递一个函数作为回调。
<script type="text/babel">
//创建组件
class Login extends React.Component{
state={username:'',password:''}
// 提交
handleSubmit=(event)=>{
event.preventDefault()//阻止默认事件
alert(`你输入的用户名是${this.state.username},你输入的密码是${this.state.password}`)
}
// 保存表单数据到状态中
saveFomeData=(type,event)=>{
this.setState({[type]:event.target.value})
}
render(){
return(
<form action="" onSubmit={this.handleSubmit}>
用户名:<input type="text" id="" placeholder="请输入用户名" name="username" onChange={(event)=>this.saveFomeData('username',event)}/>
密码:<input type="password" id="" placeholder="请输入密码" name="password" onChange={(event)=>this.saveFomeData('password',event)}/>
<button>登录</button>
</form>
)
}
}
//渲染组件到页面
ReactDOM.render(<Login/>,document.getElementById('test'))
</script>
调动时机:组件挂载完毕时调用,一般用于做一些初始化的事情。(开启定时器,发送网络请求、订阅消息)
<script type="text/babel">
//创建组件
class Life extends React.Component{
state={opacity:1}
//使组件从页面移除
distory=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 调动时机:组件挂载完毕时调用
componentDidMount(){
setInterval(()=>{
let {opacity} = this.state
opacity -= 0.1
if(opacity <= 0) opacity=1
this.setState({opacity})
},200)
}
render(){
return(
<div>
<h2 style={{opacity:this.state.opacity}}>ReactReactReactReact</h2>
<button onClick={this.distory}>消失</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Life/>,document.getElementById('test'))
</script>
调用时机:组件将要被卸载时调用,一般做一些收尾的事(关闭定时器、取消订阅消息)
卸载组件:this.distory
<script type="text/babel">
//创建组件
class Life extends React.Component{
state={opacity:1}
//使组件从页面移除
distory=()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 调用时机:组件将要被卸载时调用
componentWillUnmount(){
clearInterval(this.timer)
}
render(){
return(
<div>
<h2 style={{opacity:this.state.opacity}}>ReactReactReactReact</h2>
<button onClick={this.distory}>消失</button>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Life/>,document.getElementById('test'))
1.新版本中要用
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillMount
UNSAFE_componentWillUpdate
2 新版本出现两个新的钩子函数
使用的情况比较少
getDerivedStateFromProps
getSnapshotBeforeUpdate
getDerivedStateFromProps:从props中得到一个派生的状态,若state中的值在任何时候都取决于props中时使用【一般不使用】
// getDerivedStateFromProps
//返回值为一个对象:state中的同名值被props替代且不能被修改
//返回值为null:没有影响
//返回值为props:state中的同名值被props替代且不能被修改
static getDerivedStateFromProps(props,state){
console.log('Count-getDerivedStateFromProps',props,state)
return props
}
getSnapshotBeforeUpdate:在更新之前获取快照 【不常用】
//getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(){
console.log('Count-getSnapshotBeforeUpdate')
return 'weijiamin'//会被componentDidUpdate接收
}
步骤
1全局安装: npm install -g create-react-app/npm i create-react-app -g
2.切换到想创建项目的目录,使用命令:create-react-app hello-react
3.进入项目文件夹:cd hello-react
4 启动项目:npm start
5ts+react :create react-app xxx --template typescript
快捷键
rcc:快速生成React类式组件框架
rfc:快速生成React函数式组件
1.父-子
父组件
export default class App extends Component {
state = {
todos: [
{ id: "001", name: "吃饭" ,done:true},
{ id: "002", name: "睡觉" ,done:false},
{ id: "003", name: "上班" ,done:false},
{ id: "004", name: "打游戏" ,done:false},
],
};
render() {
return (
<List todos={this.state.todos} />
);
}
}
子组件
export default class List extends Component {
render() {
return (
<ul className="todo-main">
{
this.props.todos.map((item,index)=>{
return <Item key={item.id} todo={item}/>
})
}
</ul>
);
}
}
2.子-父:实际是方法的传递
父组件
export default class App extends Component {
a=(data)=>{
console.log('父组件接收到的值',data)
}
render() {
return (
<Header a={this.a}/>
);
}
}
子组件
export default class Header extends Component {
handleKeyUp=(event)=>{
this.props.a(event.target.value)
}
render() {
return (
<input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.handleKeyUp}/>
);
}
}
注意:当子组件调用的父组件的函数中使用到了this相关的数据的时候,绑定方法要手动指定this指向否则this会默认指向当前组件
父组件:
export default class TestA extends Component {
state={
list:['1','2','3'],
}
changeItem(index){
console.log(index,this.state)
let list = this.state.list
list[index] = '你好'
this.setState({list:list})
}
render() {
const _this = this
return (
<div>
{this.state.list.map((item,index)=>{
return <div key={index} className='list-item' onClick={()=>{this.changeItem(index)}}>{item}</div>
})}
<div>这里调用子组件</div>
<TestB list={this.state.list} changeItem={_this.changeItem.bind(this)}></TestB>
</div>
)
}
}
子组件:
export default class TestB extends Component {
changeItemByParent(index){
this.props.changeItem(index)
}
render() {
return (
<div>
{this.props.list.map((item,index)=>{
return <div key={index} onClick={()=>{this.changeItemByParent(index)}}>{item}</div>
})}
</div>
)
}
}
参考:react 子组件调用父组件的方法时,this指向子组件的问题
订阅消息:1.消息名 2.发布消息
下载:npm i pubsub-js --save
使用:
(1)import PubSub from ‘pubsub-js’ 引入
(2)let token=PubSub.subscribe(‘delete’,function(data){} 订阅 【token相对于订阅的id】
(3)PubSub.publish(‘delete’,data) 发布消息 【参数一:消息名;参数二:携带的数据】
(4)PubSub.unsubscribe(‘token’) 取消订阅 【传入订阅id取消订阅】
组件1:订阅消息
//初始化状态
state={
users:[],
isFirst:true,//是否为第一次打开页面
isLoading:false,//是否为加载状态
err:''//存储请求相关的错误信息
}
// 接收到两个参数 参数1:消息的名称 参数2:消息传递的值
componentDidMount(){
this.token=PubSub.subscribe('message',(msg,data)=>{
this.setState(data)
})
}
//取消订阅
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
组件2:发布消息
search=()=>{
//获取用户输入
let keyword = this.keyWordNode.current.value
// 发送请求前通知List更新状态
PubSub.publish('message',{isFirst:false,isLoading:true})
//发送网络请求
axios.get(`http://localhost:3000/api1/search/users?q=${keyword}`).then(
res=>{
//请求成功后通知List更新状态
PubSub.publish('message',{isLoading:false,users:res.data.items})
},
reason=>{
//请求失败后通知更新List状态
PubSub.publish('message',{isLoading:false,err:reason.message})
}
)
}
方式1
方式:修改package.josn文件
注意:要重新启动脚手架
发送请求:
axios.get('http://localhost:3000/students').then(
res=>{console.log('成功',res.data)},
error=>{console.log('失败')}
)
3000端口 要向5000端口发送请求 修改代理之后发送请求的端口改为3000
缺点: 只能配置一个代理地址,当服务端地址改变后失效
方式2
在src文件夹下面添加setupProxy.js文件
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = function (app) {
app.use(
createProxyMiddleware("/api1",{
target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api1": "" }, //重写请求路径
}),
createProxyMiddleware("/api2",{
target: "http://localhost:5001", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api2": "" },
})
)
}
发送请求
getStudentInfo = () => {
axios.get("http://localhost:3000/api1/students").then(
res => {
console.log("成功", res.data);
},
error => {
console.log("失败");
}
);
};
没看
1.SPA的理解
1.单页web应用 (single web page SPA)
2.整个应用只有一个完整的页面
3.点击页面中的链接不会刷新页面,只会做页面的局部更新
4.数据都需要通过ajax请求获取,并在前端异步呈现
2.路由的理解
一个路由就是一个映射关系(key:value)
key为路径,value可能是function或者component
注册路由
import {Link,Route } from "react-router-dom";
注意:要用BrowserRouter包裹 < App/> ,因为都需要包裹直接在index.js中操作Link,Route都是路由组件 为了规范About,Home都应该放在pages文件夹中 而不是components
所以组件可以分为 一般组件和路由组件
区别:
1 路由组件放在pages文件中,一般组件放在component文件中。
2 路由组件需要通过路由匹配调用 一般组件直接使用
3 路由组件默认会收到props,一般组件只有手动传值才会有22 封装NavLike
组件
import React, { Component } from 'react' import {NavLink } from "react-router-dom"; export default class MyNavLink extends Component { render() { return ( <NavLink className="list-group-item" activeClassName="active_nav" {...this.props}></NavLink> ) } }
NavLink可以通过指定activeClassName来指定active时的样式
这里的标签体内容【如about】会被接收到this.props.children中
接收的时候直接用 {…this.props} 来接收 可以将属性直接放在标签中使用
<MyNavLink to="/about">aboutMyNavLink> <MyNavLink to="/home">homeMyNavLink>
23 使用Switch组件控制路由匹配
作用:当匹配到一个路由后就中断匹配,提高匹配效率。
import {Route,Switch } from "react-router-dom";
//当路由为/home时 结果:只显示Home组件而不显示Test组件。 <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Route path="/home" component={Test}/> Switch>
24 多层级路由刷新后样式丢失问题解决
<div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/weijiamin/about">aboutMyNavLink> <MyNavLink to="/weijiamin/home">homeMyNavLink> div> div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Switch> <Route path="/weijiamin/about" component={About}/> <Route path="/weijiamin/home" component={Home}/> Switch> div> div> div> div>
方法1.修改pubilc/index.js中的样式引入
//不加./ <link rel="stylesheet" href="/css/bootstrap.css">
方法2使用 %PUBLIC_URL%
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
方法3 使用 HashRouter 【不常用】
import { HashRouter} from "react-router-dom"; //渲染App组件到页面 ReactDOM.render( <HashRouter> <App/> </HashRouter> ,document.getElementById('root') )
25 路由匹配规则
1.默认是模糊匹配(最左匹配)
可以匹配的情况<MyNavLink to="/about/weijiamin">aboutMyNavLink> <Route path="/about" component={About}/>
<MyNavLink to="/about">aboutMyNavLink> <Route path="/about" component={About}/>
2.开启精准匹配(全部都要相等)
exact
使用的原则:一般不使用<MyNavLink to="/weijiamin/about">aboutMyNavLink> <Route exact path="/weijiamin/about" component={About}/>
26 设置默认的路由,路由重定向-Redirect
使用Redirect【重定向】
一般写在所有路由注册的最下方,当所有路由都无法匹配的时候,跳转到Redirect指定的路由 一般用于编写默认路由
{/* 注册路由 */} <Switch> <Route path="/about" component={About}/> <Route path="/home" component={Home}/> <Redirect to="/home">Redirect> Switch>
27 嵌套路由
export default class Home extends Component { render() { return ( <div> <ul className="nav nav-tabs"> <li> <MyNavLink to="/home/news">news</MyNavLink> </li> <li> <MyNavLink to="/home/message">message</MyNavLink> </li> </ul> {/* 注册路由 */} <Switch> <Route path="/home/news" component={News}/> <Route path="/home/message" component={Message}/> <Redirect to="/home/message"></Redirect> </Switch> </div> ) }
注意:
- 注册子路由的时候要写上父路由的path值。
- 路由的匹配是按照注册路由的顺序执行的。
28 路由传参
(28-1)路由传递params参数
{/* 像路由组件传递params参数 */} <Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}Link>
{/* 声明接收params参数 */} <Route path="/home/message/detail/:id/:title" component={Detail}/>
(28-2)路由传递search参数
像路由组件传递search参数 <Link to={`/home/message/detail?id=${item.id}&title=${item.title}`}>{item.title}</Link>
{/* search参数无需声明接收 正常声明即可*/} <Route path="/home/message/detail" component={Detail}/>
import qs from 'qs' 接收saerch参数 let {search} = this.props.location let {id,title}= qs.parse(search.slice(1))
(28-3)路由接收state参数
区别:参数没有暴露在地址栏中。
像路由组件传递state参数 <Link to={{pathname:'/home/message/detail',state:{id:item.id,title:item.title}}}>{item.title}</Link>
{/* state参数无需声明接收 正常声明即可*/} <Route path="/home/message/detail" component={Detail}/>
接收state参数 const {state}=this.props.location
虽然没有在地址栏中但是刷新state参数也不会消失
因为history对象一直记录了
但是清除内存后会消失
解决:
const {state}=this.props.location||{}
29 路由的push与replace
1.push(默认状态)
路由(栈) http://localhost:3001/home/message/detail http://localhost:3001/home/message http://localhost:3001/home http://localhost:3001/about 2.replace模式
开启replace模式<Link replace={true} to="/home/message/detail">{item.title}Link>
路由(栈) http://localhost:3001/home/message/detail http://localhost:3001/home/messagehttp://localhost:3001/home http://localhost:3001/about 30 编程式路由导航
设置按钮
<button onClick={()=>{this.showReplace(item.id,item.title)}}>replace查看</button> <button onClick={()=>{this.showPush(item.id,item.title)}}>push查看</button>
showReplace=(id,title)=>{ // replace跳转 携带params参数 this.props.history.replace(`/home/message/detail/${id}/${title}`) // replace跳转 携带search参数 this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`) // replace跳转 携带state参数 this.props.history.replace('/home/message/detail',{id,title}) } showPush=(id,title)=>{ // push跳转 携带params参数 this.props.history.push(`/home/message/detail/${id}/${title}`) // push跳转 携带search参数 this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`) // push跳转 携带state参数 this.props.history.push('/home/message/detail',{id,title}) }
注意:无论是哪种方式传递,声明接收参数和接收参数的要与之匹配
31 编程式路由前进和后退
<button onClick={this.back}>回退button> <button onClick={this.forward}>前进button> <button onClick={this.goByNum}>前进或后退几部button>
back=()=>{ this.props.history.goBack() } forward=()=>{ this.props.history.goForward() } goByNum=()=>{ // 正数为前进 负数为后退 this.props.history.go(-2) }
32 withRouter的使用
withRouter可以加工一般组件,使一般组件具备路由组件特有的api
withRouter返回的是一个新组件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() } goByNum=()=>{ // 正数为前进 负数为后退 this.props.history.go(-2) } render() { return ( <div className="page-header"> <h2>React Router Demo</h2> <button onClick={this.back}>回退</button> <button onClick={this.forward}>前进</button> <button onClick={this.goByNum}>前进或后退几部</button> </div> ) } } // 暴露的是withRouter加工后的Header export default withRouter(Header)
33 BrowserRouter和HashRouter的区别
1.底层原理不同
BrowserRouter使用的H5的history API,不兼容IE9及以下
HashRouter使用的是URL的哈希值
2.url的表现形式不同
BrowserRouter:没#
HashRouter:有#
3.刷新后对路由state参数的影响
BrowserRouter:没有任何影响,因为state保存在location对象中
HashRouter:导致路由state丢失备注:HashRouter可以用于解决一些路径错误相关问题。
34组件库 ant-design
(1)基本使用
ant-design官网
安装 npm install antd --save
- 安装依赖的包
npm install --save @craco/craco
npm i craco-less
- 修改样式引入
- 在根目录下创建 craco.config.js文件并修改配置
const CracoLessPlugin = require('craco-less'); module.exports = { plugins: [ { plugin: CracoLessPlugin, options: { lessLoaderOptions: { lessOptions: { modifyVars: { '@primary-color': '#1DA57A' }, javascriptEnabled: true, }, }, }, }, ], };
4.修改package.json文件
"scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" },
5.运行
npm start
35 redux
35.0 redux概念
redux是什么?
- 是一个专门用于 管理状态 的js库,不是react插件库。
- 它可以用在react,angular,vue中,但基本与react配合。
- 作用:集中式管理react应用中多个组件 共享 的状态。
什么情况下用redux?
- 某个组件的状态需要让其他的组件可以 随时 拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体使用原则:能不用就不用,如果不用的情况比较吃力才用。
- Action Creators:创建动作对象;
- Store :管理者;
- Reducers:初始化状态、加工状态;
35.1 redux的三个核心概念
1.action
动作对象
包含两个属性:
- type:标识的属性,值为字符串,唯一,必要的属性
- data:数据的属性,值类型任意,可选属性
例:{type:‘ADD_STUDENT’,data:{name:‘tom’,age:18}}2.reducer
1.用于初始化、加工状态
2.加工时,根据旧的state和action,产生新的state的纯函数3.store
1.将state、action、reducer联系在一起的对象
2.如何得到此对象:
(1)import {createStore} from ‘redux’
(2)import reducer from ‘./reducers’
(3)const store=createStore(reducer)
3.此对象的功能:
(1)store.getState()得到state
(2)store.dispatch(action):分发action,触发reducer调用,产生新的state
(3)subscribe(listener)注册监听,当产生了新的state时自动调用35.2 redux的精简使用
纯react版本
import React, { Component } from 'react' export default class Count extends Component { state = {count:0} //加法 increment=()=>{ const {value} = this.selectNumber const {count} = this.state this.setState({count:count+value*1}) } //减法 decrement=()=>{ const {value} = this.selectNumber const {count} = this.state this.setState({count:count-value*1}) } //奇数加 incrementOfOdd=()=>{ const {value} = this.selectNumber const {count} = this.state if(count%2!==0){ this.setState({count:count+value*1}) } } //异步加 incrementOfAsync=()=>{ const {value} = this.selectNumber const {count} = this.state setTimeout(()=>{ this.setState({count:count+value*1}) },800) } render() { return ( <div> <h1>当前求和为:{this.state.count}</h1> <select ref={c=>this.selectNumber=c}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementOfOdd}>当前求和为奇数加</button> <button onClick={this.incrementOfAsync}>异步加</button> </div> ) } }
redux版本
1.下载:npm i redux
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象 // 引入createStore 用于创建store import { createStore } from "redux"; // 引入为Count组件服务的reducer import countReducer from './conut_reducer' //暴露store export default createStore(countReducer)
conut_reducer.js
//该文件是用于创建一个为count服务的reducer,reducer的本质就是一个函数 //reducer函数的两个参数 //preState:之前的状态 //action:要做的动作 //初始化状态 const initState = 0 export default function countReducer(preState=initState,action){ const {type,data} = action switch(type){ case 'increment': return preState + data case 'decrement': return preState - data default: return preState } }
注意:reducer中只写操作,判断条件等语句都写在组件中,reducers是纯函数。
count组件
import React, { Component } from "react"; //引入store用于获取store中保存的状态 import store from "../../redux/store"; export default class Count extends Component { increment = () => { //通知reducter加value const { value } = this.selectNumber; store.dispatch({type:'increment',data:value*1}) }; componentDidMount(){ //检查redux中状态的变化,只要变化就调用render store.subscribe(()=>{ this.setState({}) }) } render() { return ( <div> {/* 获取store中的值 */} <h1>当前求和为{store.getState()}</h1> <select name="" id="" ref={c => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> </div> ); } }
使用的三个注意点:
- store.getState():获取store状态值。
- store.dispatch({type:‘increment’,data:value*1}):告诉reducer动作对象
- componentDidMount(){
store.subscribe(()=>{
this.setState({})
})
}:检查redux中状态的变化,只要变化就调用render35.3 redux文件完整版
和精简版的区别在于
1.使用Action creator,不自传action
2.使用constant来规范类型constant.js
// 该模块是用于定义action对象中的type类型 防止在编写过程中出错 export const INCREMENT = 'increment' export const DECREMENT = 'decrement'
count_action.js
// 该文件专为Count组件生成action import { INCREMENT,DECREMENT } from "./constant" export const createIncrementAction = data => { return { type: INCREMENT, data }; }; export const createDecrementAction = data => { return { type: DECREMENT, data }; };
conut_reducer.js
//该文件是用于创建一个为count服务的reducer,reducer的本质就是一个函数 import { INCREMENT,DECREMENT } from "./constant" //reducer函数的两个参数 //preState:之前的状态 //action:要做的动作 export default function countReducer(preState,action){ console.log(preState,action) //初始化状态 if(preState === undefined) preState = 0 const {type,data} = action switch(type){ case INCREMENT: return preState + data case DECREMENT: return preState - data default: return preState } }
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象 // 引入createStore 用于创建store import { createStore } from "redux"; // 引入为Count组件服务的reducer import countReducer from './conut_reducer' //暴露store export default createStore(countReducer)
Count组件
import React, { Component } from "react"; //引入store用于获取store中保存的状态 import store from "../../redux/store"; //引入actionCreate 专门用于创建action对象 import { createIncrementAction,createDecrementAction } from "../../redux/count_action"; export default class Count extends Component { increment = () => { //通知reducter加value const { value } = this.selectNumber; store.dispatch(createIncrementAction(value*1)) }; decrement = () => { const { value } = this.selectNumber; store.dispatch(createDecrementAction(value*1)) }; incrementIfOdd = () => { const { value } = this.selectNumber; const count = store.getState() if (count % 2 !== 0) { store.dispatch(createIncrementAction(value*1)) } }; componentDidMount(){ //检查redux中状态的变化,只要变化就调用render store.subscribe(()=>{ this.setState({}) }) } render() { return ( <div> <h1>当前求和为{store.getState()}</h1> <select name="" id="" ref={c => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementIfOdd}>当前求和为奇数再加</button> <button onClick={this.incrementAsync}>异步加</button> </div> ); } }
35.4 异步action(复习的时候看这版)
action类型 说明 对象类型 同步action 函数类型 异步action 下载依赖:npm i redux-thunk (异步action传递到store的中间件,使store可以接收到非对象类型的action)
异步action就是指action的值为函数,异步action中一般会调用同步action,异步action不是必须要使用的
store.js
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象 // 引入createStore 用于创建store import { createStore,applyMiddleware } from "redux"; // 引入为Count组件服务的reducer import countReducer from './conut_reducer' //引入 redux-thunk用于支持异步action import thunk from "redux-thunk"; //暴露store export default createStore(countReducer,applyMiddleware(thunk))
count_action.js
//同步action export const createIncrementAction = data => { return { type: INCREMENT, data }; }; //异步action export const createDecrementAsyncAction = (data,time) => { return (dispatch)=>{ setTimeout(() => { dispatch(createIncrementAction(data)) }, time); } };
异步action提供dispatch方法
Count组件
import React, { Component } from "react"; //引入store用于获取store中保存的状态 import store from "../../redux/store"; //引入actionCreate 专门用于创建action对象 import { createDecrementAsyncAction,createDecrementAction } from "../../redux/count_action"; export default class Count extends Component { incrementAsync = () => { const { value } = this.selectNumber; store.dispatch(createDecrementAsyncAction(value*1,2000)) }; componentDidMount(){ //检查redux中状态的变化,只要变化就调用render store.subscribe(()=>{ this.setState({}) }) } render() { return ( <div> <h1>当前求和为{store.getState()}</h1> <select name="" id="" ref={c => (this.selectNumber = c)}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.incrementAsync}>异步加</button> </div> ); } }
36 react-redux
安装:npm i react-redux
36.1 react-dedux的基本使用
- 将Count改名为CountUI,且只保存html结构
- 创建容器组件 在src下新建文件containers/Count/index用于放置CountUI的容器组件
//自定义容器组件 // 引入count的UI组件 import CountUI from '../../components/CountUI' //引入connect用于链接容器组件和UI组件 import { connect } from "react-redux"; // 生成容器组件 并链接UI组件 const countContainer = connect()(CountUI) export default countContainer
3.修改App.js中的引入,并向容器组件中传递store(到这一步就实现了容器和UI组件、容器和redux之间的连接)
import React, { Component } from 'react' //渲染的是容器组件 import Count from './containers/Count' import store from './redux/store' export default class App extends Component { render() { return ( <div><Count store={store}></Count></div> ) } }
当容器组件与store连接后,UI组件的props会自动收到store
5.容器组件和redux的数据沟通:
import CountUI from '../../components/CountUI' import { connect } from "react-redux"; import {createIncrementAction} from '../../redux/count_action' //这个函数有一个参数为redux的state function a(state){ return {count:state} } //这个函数有一个参数为dispatch 用于操作store的值 function b(dispatch){ return {add:(data)=>{ //通知redux执行increase dispatch(createIncrementAction(value)) }} } const countContainer = connect(a,b)(CountUI) export default countContainer
规范命名:
- a函数:mapStateToProps
- b函数:mapDispatchToProps
import CountUI from '../../components/CountUI' import { connect } from "react-redux"; import { createIncrementAction,createDecrementAction } from "../../redux/count_action"; function mapStateToProps(state){ return {count:state} } function mapDispatchToProps(dispatch){ return { increace:data=>dispatch(createIncrementAction(data)), decreace:data=>dispatch(createDecrementAction(data)) } } const countContainer = connect(mapStateToProps,mapDispatchToProps)(CountUI) export default countContainer
6.UI组件使用props
increment = () => { const { value } = this.selectNumber; this.props.add(1*value) };
<h1>当前求和为:{this.props.count}h1>
36.2 react-dedux的优化
1.容器的优化
import CountUI from '../../components/CountUI' import { connect } from "react-redux"; import { createIncrementAction,createDecrementAction } from "../../redux/count_action"; //简写方式 const countContainer = connect( //传递state参数 state=>{return {count:state}}, //对象的形式 只需提供action 就可以自动调用dispatch { increace:createIncrementAction, decreace:createDecrementAction } ) (CountUI) export default countContainer
2.无需使用store.subscribe进行监测
react-redux自动检测并更新,容器组件默认拥有检测redux变化的能力
3.App.js中传store的优化
优化:不在App.js中传,在index.js中传
App.js
import React, { Component } from 'react' //渲染的是容器组件 import Count from './containers/Count' export default class App extends Component { render() { return ( // 给容器组件传递store <div><Count></Count></div> ) } }
index.js
import React from 'react'; import App from './App'; import { createRoot } from 'react-dom/client'; //引入store相关 import store from './redux/store'; import {Provider} from 'react-redux' const container = document.getElementById('root'); const root = createRoot(container); root.render( <Provider store={store}> <App /> </Provider> );
4.整合容器组件和UI组件为一个组件
import React, { Component } from 'react' import { connect } from "react-redux"; import {createIncrementAction,createDecrementAction,createIncrementActionAsync} from '../../redux/count_action' //UI组件 class Count extends Component { state = {count:0} //加法 increment=()=>{ const { value } = this.selectNumber; this.props.increace(value*1) } //减法 decrement=()=>{ const {value} = this.selectNumber this.props.decreace(value*1) } //奇数加 incrementOfOdd=()=>{ const {value} = this.selectNumber if(this.props.count%2!==0){ this.props.increace(value*1) } } //异步加 incrementOfAsync=()=>{ const {value} = this.selectNumber this.props.increaceAsyn(value*1,1000) } render() { console.log(this.props) return ( <div> <h1>当前求和为:{this.props.count}</h1> <select ref={c=>this.selectNumber=c}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementOfOdd}>当前求和为奇数加</button> <button onClick={this.incrementOfAsync}>异步加</button> </div> ) } } //容器组件 const countContainer = connect( //传递state状态 state=>{return {count:state}}, //传递方法 react-redux自动分发 { increace:createIncrementAction, decreace:createDecrementAction, increaceAsyn:createIncrementActionAsync } )(Count) export default countContainer
37 redux数据共享
37.1 重新修改redux文件结构
- 文件也可以直接用count.js命名、不一定要用count_action.js/count_reducer.js
- 文件内容按照redux需要来写 可以参考 “redux文件完整版”
37.2 store.js
多个组件共同使用redux的情况
// 该文件专门用于暴露一个store对象,整个应用只有一个store对象 // 引入createStore 用于创建store // applyMiddleware:使异步action可以被接收 // combineReducers:合并reducer import { createStore,applyMiddleware,combineReducers } from "redux"; // 引入为Count组件服务的reducer import countReducer from "./reducers/count_reducer"; //引入person的reducer import personReducer from "./reducers/person_reducer"; //引入 redux-thunk用于支持异步action import thunk from "redux-thunk"; //汇总reducer const allReducer= combineReducers({ count:countReducer, person:personReducer }) //暴露store export default createStore(allReducer,applyMiddleware(thunk))
37.3 constant.js
// 用于定义action对象中type的常量值 防止写错 //count export const INCREMENT = 'increment' export const DECREMENT = 'decrement' //person export const ADDPERSON = 'addperson'
37.4 action.js
person_action.js
import { ADDPERSON } from "../constant"; export const createAddPersonAction=(data)=>{ return {type:ADDPERSON,data} }
count_action.js
import { INCREMENT,DECREMENT } from "../constant"; export const createIncrementAction = data => { return { type: INCREMENT, data }; }; export const createDecrementAction = data => { return { type: DECREMENT, data }; }; export const createIncrementActionAsync = (data,time)=>{ return (dispatch)=>{ setTimeout(()=>{ dispatch(createIncrementAction(data)) },time) } }
37.5 reducer.js
person_reducer.js
import { ADDPERSON } from "../constant"; const initState=[{id:'001',name:'tome',age:15}] export default function personReducer(preState=initState,action){ const {type,data} = action switch(type){ case ADDPERSON: // return preState.push(data) return [data,...preState] default: return preState } }
count_reducer.js
// 初始化 import { INCREMENT,DECREMENT } from "../constant"; const initState = 0 export default function countReducer(preState = initState,action){ const {type,data} = action switch(type){ case INCREMENT: return preState = preState+data case DECREMENT: return preState = preState-data default: return preState } }
37.3 组件内使用
person
import React, { Component } from 'react' import {nanoid} from 'nanoid' import { connect } from 'react-redux' import { createAddPersonAction } from '../../redux/actions/person_action' class Person extends Component { addPerson =()=>{ const age = this.age.value const name = this.name.value const person = {id:nanoid(), name,age} this.props.createAddPersonAction(person) } render() { return ( <div> <h1>我是person组件</h1> <div>我拿到的count值:{this.props.count}</div> <input ref={c=>this.name=c} type="text" placeholder='输入名字'/> <input ref={c=>this.age=c} type="text" placeholder='输入年龄'/> <button onClick={this.addPerson}>添加</button> <ul> {this.props.person.map((item)=>{ return <li key={item.id}>{item.name}--{item.age}</li> })} </ul> </div> ) } } const PersonContainer = connect( state=>{return { count:state.count, person:state.person }}, { createAddPersonAction:createAddPersonAction } )(Person) export default PersonContainer
count
import React, { Component } from 'react' import { connect } from "react-redux"; import {createIncrementAction,createDecrementAction,createIncrementActionAsync} from '../../redux/actions/count_action' //UI组件 class Count extends Component { increment=()=>{ const { value } = this.selectNumber; this.props.increace(value*1) } //减法 decrement=()=>{ const {value} = this.selectNumber this.props.decreace(value*1) } //奇数加 incrementOfOdd=()=>{ const {value} = this.selectNumber if(this.props.count%2!==0){ this.props.increace(value*1) } } //异步加 incrementOfAsync=()=>{ const {value} = this.selectNumber this.props.increaceAsyn(value*1,1000) } render() { return ( <div> <h1>我是Coun组件</h1> <div>我拿到的person:</div> <ul> {this.props.person.map((item)=>{ return <li key={item.id}>{item.name}-{item.age}</li> })} </ul> <h3>当前求和为:{this.props.count}</h3> <select ref={c=>this.selectNumber=c}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button onClick={this.increment}>+</button> <button onClick={this.decrement}>-</button> <button onClick={this.incrementOfOdd}>当前求和为奇数加</button> <button onClick={this.incrementOfAsync}>异步加</button> </div> ) } } //容器组件 const countContainer = connect( //传递state参数 state=>{return {count:state.count,person:state.person}}, //传递方法 react-redux自动分发 { increace:createIncrementAction, decreace:createDecrementAction, increaceAsyn:createIncrementActionAsync } )(Count) export default countContainer
app.js
import React, { Component } from 'react' //渲染的是容器组件 import Count from './containers/Count/Count' // import Test from './containers/test' import Person from './containers/Person/Person' export default class App extends Component { render() { return ( // 给容器组件传递store <div> <Count></Count> <hr></hr> <Person></Person> </div> ) } }
38 纯函数
是一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)。
————————————————————————————
必须遵循以下规则:
(1)不得改写参数数据
(2)不会产任何副作用,例如网络请求,输入和输出设备
(3)不能调用Date.now()或者Math.randing()等不纯的方法
————————————————————————————
redux的reducer必须是一个纯函数39 redux扩展插件应用
安装库:npm install redux-devtools-extension
修改redux/store.js// 该文件专门用于暴露一个store对象,整个应用只有一个store对象 //引入redux-devtools-extension import {composeWithDevTools} from 'redux-devtools-extension' // 引入createStore 用于创建store // applyMiddleware:使异步action可以被接收 // combineReducers:合并reducer import { createStore,applyMiddleware,combineReducers } from "redux"; // 引入为Count组件服务的reducer import countReducer from './reducers/conut' //引入person的reducer import personReducer from "./reducers/person"; //引入 redux-thunk用于支持异步action import thunk from "redux-thunk"; //汇总reducer const allReducer= combineReducers({ count:countReducer, person:personReducer }) //暴露store export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))
40 打包react项目
1.执行:npm run build
生成build文件2.安装库:npm i serve -g
用于开启服务器3. 执行:serve build
以build文件夹作为根路径来启动一台服务器在工作上是用不到的
扩展1-关于setState()
(1). setState(stateChange, [callback])------对象式的setState
1.stateChange为状态改变对象(该对象可以体现出状态的更改)
2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
setState引起react更新的动作是异步的(2). setState(updater, [callback])------函数式的setState
1.updater为返回stateChange对象的函数。
2.updater可以接收到state和props。
4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。总结:
1.对象式的setState是函数式的setState的简写方式(语法糖)
2.使用原则:
(1).如果新状态不依赖于原状态 ===> 使用对象方式
(2).如果新状态依赖于原状态 ===> 使用函数方式
(3).如果需要在setState()执行后获取最新的状态数据,
要在第二个callback函数中读取1.setState()的第一种用法
//setState的第一种用法 add = () => { const {count}=this.state this.setState({count:count+1},()=>{ console.log("在回调函数中",this.state) }) console.log("不在回调函数中",this.state) };
输出结果:
不在回调函数中 {count: 0}
在回调函数中 {count: 1}2.setState()的第二种用法
add=()=>{ this.setState((state,props)=>{ return {count:state.count+1} }) }
扩展2-LazyLoad懒加载
通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
通过React的Suspense来控制组件还没有加载好时的状态
loading...
// 引入lazy,Suspense
import React, { Component,lazy,Suspense } from "react";
import {NavLink,Route } from "react-router-dom";
//lazy和import一起使用 注册可以懒加载的组件
const Home= lazy(()=>import('./Home'))
const About= lazy(()=>import('./About'))
//创建并暴露App外壳组件s
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">
{/* 在react考路由链接切换组件 */}
<NavLink className="list-group-item" to="/about" >about</NavLink>
<NavLink className="list-group-item" to="/home">home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
{/* 使用Suspense来控制组件还没有加载好时的状态,这里最好是一个loading的组件 */}
<Suspense fallback={<h1>loading...</h1>}>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Suspense>
</div>
</div>
</div>
</div>
</div>
)
}
}
什么是Hooks?
(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性
3个常用的hooks
(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()
React.useState():使函数式组件能有state和修改state
export default function Demo() {
//React.useState的参数为state的初始值
//React.useState的返回值为一个数组 第一个值为state的值 第二个值为改变state的方法
const [count,setCount] = React.useState(0);
function add() {
//setState的第一种写法
setCount(count+1)
//setState的第二种写法
setCount(count=>count+1)
}
return (
<div>
<h1>现在的值为:{count}</h1>
<button onClick={add}>点我加1</button>
</div>
);
}
注意:在更新对象类型时,切记要合并旧的状态,否则旧的状态会丢失。
const [params, setParams] = useState({
rotate: 0,
color: "#000000"
});
const handleInputChange = event => {
const target = event.target;
setParams({
...params,
[target.name]: target.value
});
};
注意:因为是要修改useState处理后的值,所有需要使用useCallback来处理,其他要进行一次浅拷贝
const emailMouseLeave = useCallback((changeIndex)=>{
let result = email.concat()
result.forEach((item,index)=>{
if(index === changeIndex){
item.isHover = false
}
})
setEmail(result)
},[email])
React.useEffect():使函数式组件具备钩子函数
export default function Demo() {
const [count,setCount] = React.useState(0);
//React.useEffect可以传入两个参数
//第一个参数 :为一个函数,这个函数就相当于一个生命周期的回调函数
//第二个参数 :为一个数组,里面的值相当于监测谁
// 不写:谁都监测,
// 空数组:谁都不监测,此时第一个参数相当于componentDidMount的回调
// [count,name]:监测count和name,此时第一个参数相当于componentDidUpdate的回调
//React.useEffect 返回值为一个函数,这个函数就相当于componentWillUnmount的回调
React.useEffect(() => {
let timer=setInterval(()=>{
setCount(count=>count+1)
},1000)
return ()=>{
clearInterval(timer)
}
},[]);
//卸载组件的回调
function unMount(){
ReactDom.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<h1>现在的值为:{count}</h1>
<button onClick={unMount}>卸载组件</button>
</div>
);
}
卸载组件:
引入:import ReactDom from ‘react-dom’
卸载:ReactDom.unmountComponentAtNode(document.getElementById(‘root’))
React.useRef():使函数组件支持ref
export default function Demo() {
const myRef=React.useRef()
function show(){
alert(myRef.current.value)
}
return (
<div>
<input type="text" name="" id="" ref={myRef}/>
<button onClick={show}>点击按钮展示内容</button>
</div>
);
}
Fragment的作用?
代替div在解析的时候会忽略掉,就不会出现不必要的真实dom外层标签了
import React, { Component,Fragment } from 'react'
import Demo from './components/4_Fragment'
export default class App extends Component {
render() {
return (
<Fragment>
<Demo></Demo>
</Fragment>
)
}
}
Fragment可以接收一个参数 key,可以用于循环遍历的时候
1.Context的作用?
用于组件间的通信,常用与【祖组件】和【后代组件】之间
使用context
1.创建context容器对象,并取出Provider
const UserNameContext=React.createContext()
const {Provider,Consumer} = UserNameContext
2.渲染子组件的时候。外面要包裹Provider,通过value属性给后代传递数据
export default class A extends Component {
state={username:'tom'}
render() {
return (
<div className='parent'>
<h3>我是A组件</h3>
<h4>我的用户名是{this.state.username}</h4>
//必须是value 不允许改名
<Provider value={this.state.username}>
<B/>
</Provider>
</div>
)
}
}
3.后代读取数据
(1)方法一:仅用于类式组件
class C extends Component {
//声明接收
static contextType=UserNameContext
render() {
console.log(this)
return (
<div className='grand'>
<h3>我是B组件</h3>
<h4>我从A组件接收到的用户名是:{this.context}</h4>
</div>
)
}
}
(2)方法二:函数式组件和类式组件都可以使用
function C(){
return (
<div className='grand'>
<h3>我是B组件</h3>
<h4>我从A组件接收到的用户名是:
<Consumer>
{
value=>{
return `${value.username}`
}
}
</Consumer>
</h4>
</div>
)
}
在应用的开发中一般不使用context,一般用它来封装react插件
component存在的两个问题:
(1)只要执行了setState即使不改变数据组件也会重新render。
(2)当组件重新render(),就会自动重新render子组件,即使子组件没有用父组件的任何东西的时候。
----》效率低
原因:
component中的shouldComponentUpdate()总是返回true
解决1:重写shouldComponentUpdate()
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, nextState); //要变化成的目标props,state
console.log(this.props, this.state); //变化前的props,state
if (this.state.carName === nextState.carName) return false;
}
解决2:不用component而用purcomponent【内部实现就是重写了shouldComponentUpdate】
使用这种方法不要直接修改数据而要产生新的数据
import React, { PureComponent } from "react";
import "./index.css";
export default class Parent extends PureComponent {
state = { carName: "car1" };
changeCar = () => {
this.setState({ carName: "car2" });
};
shouldComponentUpdate(nextProps, nextState) {
console.log(nextProps, nextState); //要变化成的目标props,state
console.log(this.props, this.state); //变化前的props,state
if (this.state.carName === nextState.carName) return false;
}
render() {
return (
<div className="parent">
<h2>我是Parent</h2>
<button onClick={this.changeCar}>点我换车</button>
<h4>我的车是:{this.state.carName}</h4>
<Child carName={this.state.carName}/>
</div>
);
}
}
class Child extends PureComponent {
// shouldComponentUpdate(nextProps, nextState) {
// console.log(nextProps, nextState); //要变化成的目标props,state
// console.log(this.props, this.state); //变化前的props,state
// if (this.props.carName === nextProps.carName) return false;
// }
render() {
return (
<div className="child">
<h2>我是Child</h2>
<h4>我接到的车是:{this.props.carName}</h4>
</div>
);
}
}
import React, { Component } from "react";
import "./index.css";
export default class Parent extends Component {
state = { username: "tom" };
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<h4>我的用户名是{this.state.username}</h4>
//传参
<A render={(name) => <B name={name}/>} />
</div>
);
}
}
class A extends Component {
state = { name: "tom" };
render() {
const {name} = this.state
console.log(this.props);
return (
<div className="child">
<h3>我是A组件</h3>
//调用子组件
{this.props.render(name)}
</div>
);
}
}
//类式组件接收
class B extends Component {
render() {
return (
<div className="grand">
<h3>我是B组件</h3>
//接收
<h4>我接到的名字:{this.props.name}</h4>
</div>
);
}
}
用于捕获组件生命周期产生的错误,一般是当后端返回的数据不正确的时候使用
错误边界:不要让一个组件的错误影响整个组件,使错误控制在一定的范围内
import React, { Component } from "react";
import Child from "./Child";
export default class Prrent extends Component {
//hasError:用于标识子组件是否产生错误
state={hasError:''}
//当parent的子组件出现错误的时候会调用getDerivedStateFromError并且携带错误信息
static getDerivedStateFromError(error){
console.log(error)
return {hasError:error}
}
//渲染组件出错会调用这个函数,一般用于做统计错误,反馈给服务器,用于通知编码人员进行bug修复
componentDidCatch(){
console.log('渲染组件出错')
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError?<h2>出错啦...</h2>:<Child />}
</div>
);
}
}
错误边界只使用与生产环境,在开发环境下还是会报错。
1.组件见的关系
- 父子
- 兄弟
- 子孙
2.组件之间的通信方式
props: (1)chilren props (2)render props
消息订阅-发布
集中式管理 redux
conText 生产者-消费者模式
3.比较好的搭配
- 父子组件:props
- 兄弟组件:消息订阅预发布、集中式管理
- 祖孙组件:消息订阅与发布、集中式管理、conText(开发用的少,封装插件用的多)
安装:npm i react-router-dom
默认使用(不区分大小写):
区分大小写使用:caseSensitive path=“/about” element={} />
import React from "react";
import {NavLink,Routes,Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
export default function App() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demoh2>
div>
div>
div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 在react考路由链接切换组件 */}
<NavLink className="list-group-item" to="/about"> aboutNavLink>
<NavLink className="list-group-item" to="/home">HomeNavLink>
div>
div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Routes>
<Route path="/about" element={> } />
<Route path="/home" element={> } />
Routes>
div>
div>
div>
div>
div>
);
}
对比route5:
(1)用Routes代替Switch包裹Route
(2)用代替
} />
基本使用(默认push形式跳转):
replace形式跳转 :replace={true}/>
1.注册路由时使用
{/* 注册路由 */}
<Routes>
<Route path="/about" element={> } />
<Route path="/home" element={> } />
} />
Routes>
2.路由跳转时使用
import React ,{useState}from 'react'
import {Navigate} from 'react-router-dom'
export default function Home() {
const [state,setState] = useState({num:1})
function addStateNum(){
setState({num:state.num+1})
}
return (
<div>
<h2>Home组件的内容</h2>
//值为2的时候跳转到about组件
{state.num === 2?<Navigate to='/about'/>:<h4>当前sum的值为:{state.num}</h4>}
<button onClick={addStateNum}>点我+1</button>
</div>
)
}
基本使用:
<NavLink className={({isActive})=>isActive?'weijiamin':'list-group-item'} to="/about"> about</NavLink>
优化:
function computedClassName({isActive}){
return isActive?'list-group-item weijiamin':'list-group-item'
}
{/* 在react考路由链接切换组件 */}
<NavLink className={computedClassName} to="/about"> aboutNavLink>
<NavLink className={computedClassName} to="/home">HomeNavLink>
import {useRoutes} from "react-router-dom";
路由表
const elements=useRoutes([
{path:'/about',element:<About/>},
{path:'/home',element:<Home/>},
{path:'/',element:<Navigate to={'/about'}/>},
])
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demoh2>
div>
div>
div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 注册路由链接 */}
<NavLink className="list-group-item" to="/about"> aboutNavLink>
<NavLink className="list-group-item" to="/home">HomeNavLink>
div>
div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
{elements}
div>
div>
div>
div>
div>
一般是把路由表的数据放在文件 src/routers/index.js 中,再在组件中引入
import Home from "../pages/Home";
import About from "../pages/About";
import { Navigate } from "react-router-dom";
const routes = [
{ path: "/about", element: <About /> },
{ path: "/home", element: <Home /> },
{ path: "/", element: <Navigate to={"/about"} /> },
];
export default routes;
import React from "react";
import {NavLink,useRoutes} from "react-router-dom";
import routes from "./routes";
export default function App() {
const elements=useRoutes(routes)
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">
{/* 注册路由链接 */}
<NavLink className="list-group-item" to="/about"> about</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
{/*
} />
} />
} />
*/}
{elements}
</div>
</div>
</div>
</div>
</div>
);
}
注册路由表
{
path: "/home",
element: <Home />,
children: [
{ path: "news", element: <News /> },
{ path: "message", element: <Message /> },
],
},
import React from 'react'
import {NavLink,Outlet} from 'react-router-dom'
<div>
<h2>Home组件的内容h2>
<div>
<div className="list-group">
<NavLink className="list-group-item" to="news">aboutNavLink>
<NavLink className="list-group-item" to="message">aboutNavLink>
div>
<div>
{/* 指定路由组件呈现的位置 */}
<Outlet/>
div>
div>
div>
1.传递params参数:
声明传递params参数
<NavLink to={`detail/${item.id}/${item.title}/${item.content}`}>{item.title}NavLink>
声明接收params参数
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail/:id/:title/:content",
element: <Detail />,
},
],
},
1.传递search参数:
声明传递search参数:
<NavLink to={`detail?id=${item.id}&title=${item.title}&content=${item.content}`}>{item.title}</NavLink>
声明接收search参数:
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail",
element: <Detail />,
},
],
},
setSearch的用法:
import React from "react";
import { useSearchParams } from "react-router-dom";
export default function Detail() {
const [search,setSearch] = useSearchParams()
return (
<div>
<ul>
<li>
<button onClick={()=>{setSearch('id=11&title=哈哈&content=呜呜')}}>点我更新收到的serch参数</button>
</li>
{/* 调用serrch身上的get方法来获值 */}
<li>{search.get('id')}</li>
<li>{search.get('title')}</li>
<li>{search.get('content')}</li>
</ul>
</div>
);
}
3.传递state参数
声明传递state参数:
<NavLink to="detail" state={{id:item.id,title:item.title,content:item.content}}>
{item.title}
NavLink>
声明接收state参数:
{
path: "message",
element: <Message />,
children: [
{
// 声明接收params参数
path: "detail",
element: <Detail />,
},
],
},
可以用于路由组件和非路由组件
import { NavLink,Outlet,useNavigate} from "react-router-dom";
const navigate = useNavigate()
function toDeatil(item){
//第一个参数是要跳转的路径、第二个参数是一个配置对象
navigate('detail',{
replace:false,
//目前只能携带state参数 如果要携带其他类型的参数,拼在路径中
state:{
id:item.id,
title:item.title,
content:item.content
}
})
}
function goForward(){
navigate(1)
}
function goBack(){
navigate(-1)
}
用于判断当前组件是否处于
的上下文环境中,如果是,则返回true否则返回false
import { useInRouterContext} from "react-router-dom";
console.log(useInRouterContext())
作用:用于返回当前的导航类型(用户是如何来到当前页面的)
返回值:POP、PUSH、REPLACE
备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)
import { useNavigationType} from "react-router-dom";
console.log(useNavigationType())
作用:用于返回当前组件中渲染的嵌套路由
说明:
如果嵌套的路由组件还没有挂载,则返回null
如果嵌套的路由组件已经挂载,则展示嵌套的路由对象
import { useOutlet} from "react-router-dom";
console.log(useOutlet())
作用:给一个url值,解析其中的:path,search,hash值
import { useResolvePath} from "react-router-dom";
console.log(useResolvePath('/user?id=88&name=777'))
输出内容:
{hash: “”
pathname: “/user”
search: “?id=88&name=777”}