JSX时createElement()方法的语法糖,jsx语法被@babel/preset-react插件编译为createElement()方法。
js表达式 & js代码(js语句)区别:
表达式: 一个表达式会产生一个值,可以放在任何一个需要值的地方
1. a (变量);
2. a+b;
3. demo(1);
4. arr.map();
5. function test(){};
语句:
if(){};
for(){};
switch(val){ case:nnn };
let isLoading = true;
let result = true;
// if else
// let loadData = ()=>{
// if(isLoading){
// return <div>loading</div>
// }
// return <div>data</div>
// }
// 三元
let loadData = ()=>{
return isLoading ? (<div>loading... {result?<span>true</span>:<span>false</span>} </div>):(<div>data</div>)
}
// 与运算符
// let loadData = ()=>{
// return isLoading && (<div>loading...</div>)
// }
const title = (
<h5>条件渲染 { loadData() }</h5>
)
ReactDOM.render(title, document.getElementById('root'));
state的值是一个对象 -- 组件三大核心属性之一
注意: 组件中render方法中的this是组件实例对象/实例对象/组件对象;
组件自定义的方法中this为undefined;
a: 通过.bind(this)强制绑定实例对象给自定义方法 or b: 自定义方法改成箭头函数
class Mycomponent extends React.Component{
state = {
isHot: true, wind:'微风',mouse:false,
}
constructor(props){ //构造器只初始化调用一次
super(props);
//this.state = { isHot: true, wind:'微风' };
//2. 通过.bind(this:此时this是实例对象)来绑定changeWeather的this为实例对象,
//并返回新函数给实例对象的changeWeather属性
this.changeWeather = this.changeWeather.bind(this);
}
render(){
console.log(this); //实例对象
var {isHot,wind} = this.state;
return (
<div>
<span style={{background: this.state.mouse?'#ddd':'#fff', display:'none'}}> ccc </span>
<button onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</button>
</div>
);
}
changeWeather = () => {
console.log(this); // <--没有采用解决方法的话undefined;
this.setState({isHot:!this.state.isHot});
}
}
//渲染组件到页面
ReactDOM.render(<Mycomponent/>, document.getElementById('test'));
函数式组件
function Demo(props){ //函数式组件 16.版本前只能玩props. state,refs玩不了
console.log(this); //undefined 因为babel编译开启了严格模式:禁止自定义函数里的this指向window;
return <h2>函数式组件适用于简单组件,{props.name},性别:{props.sex},年龄:{props.age}</h2>
}
Demo.propTypes = {
name:PropTypes.string.isRequired, //name是字符串类型并且是必传的
age:PropTypes.number, //age是数字类型
speak:PropTypes.func, //speak是函数类型
}
Demo.defaultProps = {
sex:'no'
}
ReactDOM.render(<Demo name="tom" age={33}/>, document.getElementById('test'));
/*
执行ReacDOM.render(<Demo/>)之后,React解析组件标签,找到Demo组件,
发现组件是函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,呈现在页面上
*/
props: 每个实例对象都会有props属性,通过标签属性从组件外向组件内传递数据,并保存在props对象中 -- 组件三大核心属性之一
注意: props是只读模式,不可修改;
![](https://img.haomeiwen.com/i20409039/8a2c9c1091846c3d.png)
class Person extends React.Component{
state = { } //本身在函数中 this.props可直接调用,所以几乎会简写
// constructor(props){
super(props); //如果不需要实例对象操作<Person>标签上传来的属性,可简化constructor;
console.log(this.props);
// state = { isHot: true }
// this.changeWeather = this.changeWeather.bind(this); //为自定义函数绑定实例对象
// }
//static 给类本身添加属性和方法
static propTypes = { //限制标签属性类型
name: PropTypes.string.isRequired,
age: PropTypes.number,
speak: PropTypes.func,
getObj: PropTypes.shape({ //特定结构对象
color: PropTypes.string,
})
}
static defaultProps = { //默认属性
sex:'no'
}
render(){
var {name,age,sex} = this.props;
// this.props.name = 'mmm'; 无法修改, props是只读模式
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age+1}</li>
<li>性别:{sex}</li>
</ul>
)
}
}
const obj = { name:'tom', age:11, sex:'male' }
function getSpeak(){ return 'bunanbunv' }
//渲染组件到页面
ReactDOM.render(<Person {...obj} name="jerry" age={12} speak={getSpeak} />, document.getElementById('test2'));
refs & 事件处理
class Person extends React.Component{
state = {isHot: true};
blurValue = ()=>{
const {inp} = this.refs;
console.log(inp.value);
}
//回调函数模式
showValue = ()=>{
const {inp2} = this;
console.log(inp2.value);
}
myRef = React.createRef(); //(专人专用)
getValue = ()=>{
console.log(this.myRef.current.value);
}
myRef2 = React.createRef();
getValue2 = ()=>{
console.log(this.myRef2.current.value);
}
render(){
const {isHot} = this.state;
return (
<ul>
{/*1. string类型ref*/}
<li> <input ref="inp" type="text" onBlur={this.blurValue}/> </li>
{/*2. 回调函数类型ref*/}
<li>
<input ref={ currentNode=>{this.inp2 = currentNode}}/> //将当前DOM节点赋值给this.inp2;
<button onClick={this.showValue}>click</button>
</li>
{/*3. createRef类型,该容器只能专用,如多个ref需要创建多个myRef = React.createRef();*/}
<li>
<input ref={ this.myRef }/>
<button onClick={this.getValue}>click</button>
</li>
<li>
<input onBlur={this.getValue2} ref={this.myRef2}/>
</li>
</ul>
)}
}
//渲染组件到页面
ReactDOM.render(<Person name="tom" />, document.getElementById('test2'));
事件处理
a. React使用的是自定义事件,而不是原生的DOM事件. 原生:onclick react: onClick --为了更好的兼容性
b. React的事件是通过事件委托给组件根元素上。 多个li冒泡给ul --为了高效
event.target得到发生事件的Dom元素对象. --发生事件的元素正好是需要操作的元素可省略; event.target指的是触发事件的元素标签
myRef = React.createRef();
getValue = ()=>{
alert(this.myRef.current.value);
}
// myRef2 = React.createRef();
getValue2 = (event)=>{
console.log(event.target.value); //当发生事件的元素是需要操作的元素可省略
}
render(){
const {isHot} = this.state;
return (
<ul>
<li>
<input ref={ this.myRef }/>
<button onClick={this.getValue}>展示左侧数据</button>
</li>
<li>
<input onBlur={this.getValue2}/>
</li>
</ul>
)
}
表单中的 受控组件 & 非受控组件
受控: 页面中表单元素; 实时setState到state中,点击提交再从state获取数据 -- 类似vue的双向绑定;
非受控: 点击提交直接从input节点中获取value值, 并且每一个输入节点都需要ref
![](https://img.haomeiwen.com/i20409039/80e2551d9d0d73e6.png)
![](https://img.haomeiwen.com/i20409039/a373103061b33341.png)
![](https://img.haomeiwen.com/i20409039/68b022641928e3f1.png)
![](https://img.haomeiwen.com/i20409039/51853f738b8c5bdb.png)
生命钩子函数
![](https://img.haomeiwen.com/i20409039/10a4150ccae8c7bf.png)
componentDidMount: 发ajax请求,设置订阅 / 启动定时器,手动更改真实DOM
componentWillUnmount:在组件卸载前做一些收尾工作, 比如清除定时器/取消订阅等
0.组件初始化挂载流程:
1.constructor 2.componentWillMount 3.render 4.componentDidMount
2.点击+1按钮,状态更新流程:
1.shouldComponentUpdate 2.componentWillUpdate 3.render 4.componentDidUpdate
3.点击force+1按钮, 强制更新流程:
1.componentWillUpdate 2.render 3.componentDidUpdate
1.父组件向子组件传值,更新流程:
1.componentWillReceiveProps 2. shouldComponentUpdate 3.componentWillUpdate 4.render
5.componentDidUpdate
class Count extends React.Component{
state = { count: 0}
constructor(props){
super(props);
console.log('constructor');
}
add = ()=>{
var {count} = this.state;
this.setState({count: count+1})
}
force = ()=>{
this.forceUpdate();
}
death = () =>{
//卸载组件
ReactDom.unmountComponentAtNode(document.getElementById('test'));
}
//组件将要挂载钩子
componentWillMount(){
console.log('componentWillMount');
}
//组件是否能被更新; 默认省略,并且默认为true;
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return true;
}
componentWillUpdate(){
console.log('componentWillUpate');
}
render(){
console.log('render')
var {count} = this.state;
return(
<div>
<h2>{count}</h2>
<button onClick={this.add}>+1</button> <br/>
<button onClick={this.force}>force+1</button> <br/>
<button onClick={this.death}>卸载</button>
</div>
)
}
//组件更新完的钩子; preProps标签传递来的值; prevState:更新之前的值;
componentDidUpdate(preProps, prevState){
console.log('componentDidUpdate',preProps, prevState);
}
//组件挂载完成后钩子
componentDidMount(){
console.log('componentDidMount');
}
}
ReactDOM.render(<Count setCount={20}/>, document.getElementById('test'))
class Father extends React.Component{
state = {name: 'bents'}
changeName = ()=>{
this.setState({ name: 'bmw' })
}
render(){
var setName = this.state.name;
return(
<div>
<h3>father</h3>
<button onClick={this.changeName}>change</button>
<Son carName={setName} />
</div>
)
}
}
class Son extends React.Component{
//子组件初始化的时候是不调用该钩子函数的,只有在父组件传改变值的时候调用
componentWillReceiveProps(props){
console.log(props);
console.log('componentWillReceiveProps');
}
//组件是否能被更新; 默认省略,并且默认为true;
shouldComponentUpdate(){
console.log('shouldComponentUpdate');
return true;
}
componentWillUpdate(){
console.log('componentWillUpate');
}
render(){
console.log('son-render')
return(
<h5>son,它的名字是:{this.props.carName}</h5>
)
}
componentDidUpdate(){
console.log('componentDidUpdate');
}
}
ReactDOM.render(<Father/>, document.getElementById('test'))
![](https://img.haomeiwen.com/i20409039/e4fc31998fb441be.png)
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state)
return 'ccc';
}
render(){
console.log('render')
return(
<ul ref="list">
{
this.state.newsArr.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
)
}
//快照钩子返回值给 componentDidUpdate声明周期钩子 第三参;
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return this.refs.list.scrollHeight;
}
//组件更新完的钩子;
//1参: 组件更新前路由信息(路由信息是通过props传递给组件的; props: history(.push路由跳转), location(.pathname路由信息), match);
//2参: 组件更新前 state的值;
//3参: getSnapshotBeforeUpdate return的值
componentDidUpdate(preProps, prevState, listHeight){
console.log('当前路由信息:', this.props.location.pathname)
console.log('当前state:', this.state)
console.log(listHeight);
}
//组件已挂载钩子
componentDidMount(){
console.log(this.refs.list.scrollHeight);
}
使用脚手架创建react项目模板
1.全局安装(只需要第一次安装): npm i -g create-react-app
2.切换到想要创建的目录: create-react-app 项目名
3.npm start
![](https://img.haomeiwen.com/i20409039/5aa7b84aaf05e920.png)
// 某个组件使用,放在自身state; 某些组件使用,放在共同父组件state中
//状态在哪儿,操作状态的方法在哪儿;
//父组件
state={
todoList:[
{id:0,name:'吃饭',isDone:true},
]
}
//全选
checkAllTodo = (bool)=>{
const {todoList} = this.state;
const newTodoList = todoList.map(item=>{ //返回一个新的被改变值之后的数组
return {...item, isDone: bool}
})
this.setState({todoList: newTodoList});
}
clearAllDone = ()=>{
const {todoList} = this.state;
const newTodoList = todoList.filter(item=>{ //返回数组(符合判断条件的元素),没有返回[]
return !item.isDone
})
this.setState({todoList: newTodoList});
}
render() {
return (
<div className="todo-container">
<div className="todo-wrap">
//传递的参数是父组价的一个函数
<Footer todos={this.state.todoList} checkAllTodoList={this.checkAllTodo}/>
</div>
</div>
)
}
在脚手架中默认没有安装属性类型限制: npm i prop-types;
import PropTypes from 'prop-types'
export default class Footer extends Component {
static propTypes = {
todos: PropTypes.array.isRequired,
checkAllTodoList: PropTypes.func.isRequired,
clearAllDone: PropTypes.func.isRequired,
}
//this.props 父传子
// this.props.addTodo子组件调用父组件的函数,并传递实参
changeAllChecked = (event)=>{
this.props.checkAllTodoList(event.target.checked);
}
clear = ()=>{
this.props.clearAllDone();
}
render() {
const {todos} = this.props;
let count = 0;
for(var i of todos){
if(i.isDone){
count+=1;
}
}
let amount = todos.length;
return (
<div className="todo-footer">
<label>
<input type="checkbox" onChange={this.changeAllChecked} checked={count===amount && amount!==0 ?true:false}/>
</label>
<span>
<span>已完成{count} </span> / 全部 {amount}
</span>
<button className="btn btn-danger" onClick={this.clear}>清除已完成任务</button>
</div>
)
}
}
消息订阅&发布
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import axios from 'axios'
export default class Search extends Component {
search = ()=>{
//获取用户的输入(连续解构赋值+重命名)
const {keyWordElement:{value:keyWord}} = this
//发送请求前通知List更新状态
PubSub.publish('atguigu',{isFirst:false,isLoading:true})
//发送网络请求
axios.get(`/api1/search/users?q=${keyWord}`).then(
response => {
//请求成功后通知List更新状态
PubSub.publish('atguigu',{isLoading:false,users:response.data.items})
},
error => {
//请求失败后通知App更新状态
PubSub.publish('atguigu',{isLoading:false,err:error.message})
}
)
}
render() {
return (
<section className="jumbotron">
<h3 className="jumbotron-heading">搜索github用户</h3>
<div>
<input ref={c => this.keyWordElement = c} type="text" placeholder="输入关键词点击搜索"/>
<button onClick={this.search}>搜索</button>
</div>
</section>
)
}
}
import React, { Component } from 'react'
import PubSub from 'pubsub-js'
import './index.css'
export default class List extends Component {
state = { //初始化状态
users:[], //users初始值为数组
isFirst:true, //是否为第一次打开页面
isLoading:false,//标识是否处于加载中
err:'',//存储请求相关的错误信息
}
componentDidMount(){
this.token = PubSub.subscribe('atguigu',(_,stateObj)=>{
this.setState(stateObj)
})
}
componentWillUnmount(){
PubSub.unsubscribe(this.token)
}
render() {
const {users,isFirst,isLoading,err} = this.state
return (
<div className="row">
{
isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
isLoading ? <h2>Loading......</h2> :
err ? <h2 style={{color:'red'}}>{err}</h2> :
users.map((userObj)=>{
return (
<div key={userObj.id} className="card">
<a rel="noreferrer" href={userObj.html_url} target="_blank">
<img alt="head_portrait" src={userObj.avatar_url} style={{width:'100px'}}/>
</a>
<p className="card-text">{userObj.login}</p>
</div>
)
})
}
</div>
)
}
}
react脚手架配置代理(解决跨域问题)
方法一:在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二
1.在src下创建配置文件:src/setupProxy.js
2. 编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
react-router-dom单页面应用: 整个应用只有一个完整的页面,点击页面中的链接不会刷新页面,只会做页面的局部刷新。
前端路由是一套映射规则,在React中,是URL路径与组件的匹配。
脚手架默认没有安装,需npm install react-router-dom
1.导航区a标签改为Link标签, Link标签浏览器也会渲染成a标签
2.展示区Route标签进行路径匹配
3.Router包裹整个应用,一个React应用只需要使用一次: <BrowserRouter>或<HashRouter>: hash: #/home
4.一般组件:只接收标签上传的属性; 路由组件: 匹配上时接收三个固定属性history:{go,goBack,goForward,replace}, location:{pathname,search,state}, match:{params,path,url}
5.Navlink&二次封装Navlink
6.Switch
7.多级路由导致的样式丢失(未写)
8.路由的模糊匹配 & 精准匹配
9.Redirect重定向 or path="/"默认路由
10.嵌套路由
11.路由传递参数
12.编程式路由跳转(路由组件)
13.withRouter(一般组件实现前进后退)
import React, { Component } from 'react' -----App.js
import {Link,NavLink,Switch,Redirect,Route} from 'react-router-dom'
import MyNavlink from './components/MyNavlink' --一般组件(对Navlink二次封装)
import Header from './components/Header' --一般组件
import Home from './components/Home' --路由组件
import About from './components/About'
export default class App extends Component {
render() {
return (
<div>
<Header title={'ccccc'}/>
<div className="main">
<div>
{/* html中,靠<a>跳转不同的页面 */}
{/* <a href="./about.html">About</a> */}
{/* 在React中靠路由链接实现切换组件 */}
{/* <Link to="/about">About</Link> */}
{/*NavLink作用是实现高亮 <NavLink activeClassName="link-active" to="/about">About</NavLink> 还需要写.link-active的css */}
<MyNavlink to="/about">About</MyNavlink>
<MyNavlink to="/home">Home</MyNavlink>
<MyNavlink to="/home/a/b">Home--模糊匹配</MyNavlink>
</div>
<div>
{/* 注册路由:方式一: exact精确匹配: 完全一致的时候才会匹配上该组件 */}
<Switch>
<Route exact path="/" component={About}/>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
{/*栗子: /home/merbers/father 会模糊匹配上Home组件; 模糊匹配只要以path开头一致就会匹配上该组件 */}
</Switch>
</div>
<div>
{/* 注册路由:方式二 Switch: 匹配上就截止;exact精准匹配会导致无法匹配二级路由*/}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/> //当未匹配上 以上的路由,就重定向为一个默认路由
</Switch>
</div>
</div>
</div>
)
}
}
import React from 'react'; -----index.js
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
);
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可以用于解决一些路径错误相关的问题。
import React, { Component } from 'react' ----二次封装的MyNavlink
import {NavLink} from 'react-router-dom'
export default class MyNavlink extends Component {
render() {
{/* 父组件调用<MyNavlink to="/about">About</MyNavlink>; this.props。childern获取标签体文本about */}
return <NavLink activeClassName="link-active" {...this.props}></NavLink>
}
}
import React, { Component } from 'react' ----Home.jsx二级路由 路由嵌套
import {Switch,Redirect,Route} from 'react-router-dom'
import MyNavlink from '../../components/MyNavlink'
import News from './News'
import Message from './Message'
export default class Home extends Component {
render() {
return (
<div>
<h3>Home组件内容</h3>
<ul>
<li> <MyNavlink to="/home/news">News</MyNavlink> </li>
<li> <MyNavlink replace to="/home/message">Message</MyNavlink> </li>
</ul>
<Switch>
<Route path="/home/news" component={News}/>
<Route path="/home/message" component={Message}/>
<Redirect to="/home/news"/>
</Switch>
</div>
)
}
}
import React, { Component } from 'react' 想路由组件传递params参数并接收
import {Link,Route} from 'react-router-dom'
import Detail from './Detail'
export default class Message extends Component {
state={
messageArr:[
{id:1,title:'message1'},
{id:2,title:'message2'},
{id:3,title:'message3'},
]
}
back= ()=>{
this.props.history.goBack();
this.props.history.goForward();
this.props.history.go(1);
}
replaceShow = (id,title)=>{
this.props.history.push('/home);
// params 编程式路由跳转
this.props.history.replace(`/home/message/detail/${id}/${title}`);
// search 编程式路由跳转
this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`);
// state 编程式路由跳转
this.props.history.replace(`/home/message/detail`,{id,title});
}
render() {
let {messageArr} = this.state;
return (
<div>
<ul>
{
messageArr.map((item=>{
return (
<li key={item.id}>
{/* params 方案 */}
{/* <Link to={`/home/message/detail/${item.id}/${item.title}`}>{item.title}</Link> */}
{/*search 方案 */}
{/* <Link to={`/home/message/detail?id=${item.id}&title=${item.title}`}>{item.title}</Link> */}
{/* state 方案 */}
<Link replace to={{pathname:'/home/message/detail',state:{id:item.id,title:item.title}}}>{item.title}</Link>
</li>
<li>
<button onClick={()=>{this.pushShow(item.id,item.title)}}>push</button>
<button onClick={()=>{this.replaceShow(item.id,item.title)}}>replace</button>
</li>
<li>
<button onClick={this.back}>back</button>
</li>
)
}))
}
</ul>
<hr/>
{/*params方案 this.props.match.params; 接收参数*/}
<Route path="/home/message/detail/:id/:title" component={Detail}/>
{/*search 方案; import qs from 'querystring'; this.props.location; let {id} = qs.parse(search.slice(1))*/}
<Route path="/home/message/detail" component={Detail}/>
{/*state 方案 this.props.location.state; HashRouter刷新后会导致路由state参数的丢失!!!*/}
<Route path="/home/message/detail" component={Detail}/>
</div>
)
}
}
import React, { Component } from 'react' -------header.jsx
import {withRouter} from 'react-router-dom'
class Header extends Component {
back= ()=>{
this.props.history.go(1);
this.props.history.goBack();
this.props.history.goForward();
}
render() {
return (
<div className="head">
<h2>React Router Demo</h2>
<button onClick={this.back}>back</button>
</div>
)
}
}
export default withRouter(Header) //withRouter接收一个组件并返回一个新组件,新组件带有history,location,match方法
redux
是专门于用作状态管理的JS库(不是react插件库), 集中式管理react应用中多个组件共享的状态。
应用场景: 某个组件的状态,需要让其他组件共享。一个组价需要改变(通信)另外一个组件的状态;
![](https://img.haomeiwen.com/i20409039/deada5ce90691b5c.png)
action: 动作的对象。
-type: 标识属性,值为字符串,唯一必要属性。
-data: 数据属性,值类型任意,可选属性。
reducer: 用于初始化状态、加工状态,根据旧的state&action,产生新的state的纯函数。
store: 将state、action、reducer联系在一起的对象
用于定义action对象中type类型的常量值 ----constant.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
//引入createStore, 专门用于创建redux中最为核心的store对象 ----store.js
import {createStore,applyMiddleware,combineReducers} from 'redux'
import countReducer from './count_reducer'
import personReducer from './person_reducer'
import thunk from 'redux-thunk' ---异步action需引入该插件并引入applyMiddleware
const allReducer = combineReducers({
sum:countReducer,
people:personReducer
})
export default createStore(allReducer, applyMiddleware(thunk));
reducer函数会接到两个参数,分别为: preState(之前的状态), action(动作对象) -----reducer.js
import {INCREMENT,DECREMENT} from './constant'
let 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;
}
}
//该文件专为组价返回生成返回 action对象!! ----action.js
import {INCREMENT,DECREMENT} from './constant'
import store from './store';
export const createIncrementAction = data => { return {type:INCREMENT,data} }
export const createDecrementAction = data => { return {type:DECREMENT,data} }
export const createIncrementAsyncAction = (data,time) => {
return (dispatch)=>{
setTimeout(() => {
dispatch( createIncrementAction(data) ); //原本store.dispatch();因为store会调用回调函数,并传入了dispatch
}, time);
}
}
import React, { Component } from 'react' ----Count.jsx
import store from '../../redux/store'
import {createIncrementAction,createDecrementAction,createIncrementAsyncAction} from '../../redux/count_action'
export default class Count extends Component {
increment = ()=>{
let getValue = this.selectNumber.value*1;
// let {count} = this.state;
// this.setState({count:count+getValue});
store.dispatch( createIncrementAction(getValue) ); //得到对象 {type:'increment',data:value*1}
}
decrement = ()=>{
let getValue = this.selectNumber.value*1;
store.dispatch(createDecrementAction(getValue));
}
incrementAsync = ()=>{
let getValue = this.selectNumber.value*1;
// setTimeout(() => {
// store.dispatch(createIncrementAction(getValue));
// }, 500);
store.dispatch(createIncrementAsyncAction(getValue, 500));
}
componentDidMount(){
//检测redux中状态的变化,只要变化,就调用render
//or 直接在index.js中检测改变,就重新调用app组件
store.subscribe(()=>{
this.setState({});
})
}
render() {
return (
<div>
<h2>值为: {store.getState()}</h2>
<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.incrementAsync}>异步加</button>
</div>
)
}
}
import React from 'react'; -----or 直接在index.js中检测改变,就重新调用app组件
import ReactDOM from 'react-dom';
import App from './App'
import store from './redux/store'
ReactDOM.render(<App/>, document.getElementById('root') );
store.subscribe(()=>{
ReactDOM.render(<App/>, document.getElementById('root') );
})
react-redux
UI组件: 不能使用任何redux的api,只负责页面的呈现、交互等。
容器组件: 负责和redux通信,将结果交给UI组件。
创建一个容器组件----react-redux的connect函数
connect( mapStateToProps映射状态, mapDispatchToProps映射操作方法 )(UI组件)
![](https://img.haomeiwen.com/i20409039/cb100d17cf73ccb3.png)
import React from 'react'; ----index.js
import ReactDOM from 'react-dom';
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'
//引入App组件
ReactDOM.render(
//Provider为所有容器组件提供 store的state数据
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);
// store.subscribe(()=>{ react-redux的connect自动检测,可以忽略不写了
// ReactDOM.render(<App/>, document.getElementById('root') );
// })
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {createIncrementAction, createAddPersonAction} from '../../redux/count_action'
// function mapStateToProps(state){
// //mapStateToProps函数返回的是对象,对象中key传递给UI组件props--状态
// return {count:state}
// }
// function mapDispatchToProps(dispatch){
// //mapDispatchToProps函数返回的是对象,用于传递操作状态的方法
// return {
// add: val => dispatch(createIncrementAction(val)),
// }
// }
//定义UI组件
class Count extends Component {
increment = ()=>{
let getValue = this.selectNumber.value*1;
this.props.add(getValue);
}
render() {
return (
<div>
<h2>值为: {this.props.count}; 有{this.props.folks.length}人</h2>
<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.incrementAsync}>异步加</button>
</div>
)
}
}
//创建并暴露Count的容器组件
// export default connect(mapStateToProps, mapDispatchToProps)(Count);
//简写: 自动dispatch
export default connect(
state=>{return {count: state.sum, folks: state.people}},
{ add: createIncrementAction, jia: createAddPersonAction })(Count);
setState
setState是异步更新数据,同一个函数多次调用setState,只执行一次render
this.state = {count:1}
this.setState({ //第一种方式多次调用setState,state还是初始值。
count: this.state.count+1
}, ()=>{
console.log(this.state.count) //2 第二个参数是立即的(也是DOM节点渲染后执行)
})
console.log(this.state.count) //1
this.setState((state, props)=>{ //第二种方式获取到的state就是更新后的值
return { count: state.count+1 }
})
项目优化 --- UI框架按需加载
项目优化 --- react-virtualized长列表优化
项目优化 --- 使用纯组件,避免不必要的更新组件
项目优化 --- 路由组件lazy (只有在访问该组件时才加载该组件内容,提高首屏加载速度)
import React, { Component,lazy,Suspense } from 'react'
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
import Loading from './Loading'
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<Loading/>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
Hook
Hook是React 16.8.0版本增加的新特性/新语法
State Hook: React.useState可以让你在函数组件中使用 state
Effect Hook: React.useEffect 可以在函数组件中模拟类组件中的生命周期钩子(componentDidMount, componentDidUpdate, componentWillUnmount)
Ref Hook: React.createRef 可以在函数组件中存储/查找组件内的标签或任意其它数据
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
class Count extends Component {
state = {clock:0,count: 0}
add = ()=>{
this.setState(state=>({count: state.count+1}))
}
showInfo = ()=>{
let {inp} = this;
alert(inp.value);
}
unMount = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}
componentWillUnmount(){
clearInterval(this.timer);
}
componentDidMount(){
this.timer = setInterval(()=>{
this.setState(state=>({clock: state.clock+1}))
},1000)
}
render() {
return (
<div>
<h4>定时器{this.state.clock}</h4>
<h4>加{this.state.count}</h4>
<button onClick={this.add}>+1</button>
<button onClick={this.unMount}>卸载组件</button>
<input type="text" ref={c=>{this.inp = c}}/>
<button onClick={this.showInfo}>显示输入的信息</button>
</div>
)
}
}
function Count(){
let [clock, setClock] = React.useState(0); //第1个为内部当前状态值;第2个为更新状态值的函数;useState(初始值)
let [count, setCount] = React.useState(0);
let [name, setName] = React.useState('tom');
let myRef = React.useRef();
function add(){
setCount(count => count+1);
setName('jack');
}
function showInfo(){
alert(myRef.current.value);
}
function unMount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}
//空数组, DidMount调用该函数
//没有传数组 or 指定数组变量, DidMount & DidUpdate都调用该函数;
React.useEffect(()=>{
let timer = setInterval(() => {
setClock(clock => clock+1);
}, 1000);
return ()=>{ //return返回的函数相当于componentWillUnmount
clearInterval(timer)
}
},[]);
return (
<div>
<h4>定时器{clock}</h4>
<h4>加{count}</h4>
<h5>名字{name}</h5>
<button onClick={add}>+1</button>
<button onClick={unMount}>卸载组件</button>
<input type="text" ref={myRef}/>
<button onClick={showInfo}>显示输入的信息</button>
</div>
)
}
export default Count
Context
一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信(较少用)
import React, { Component } from 'react'
const MyContext = React.createContext();
const {Provider,Consumer} = MyContext;
class A extends Component {
state = {name:'tom',age:10}
render() {
let {name,age} = this.state;
return (
<div>
<h5>grandpa</h5>
<h6>名字: {name}</h6>
<Provider value={{name,age}}>
<B/>
</Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<h5>parent</h5>
<C/>
</div>
)
}
}
// class C extends Component {
// static contextType = MyContext; //声明接收context
// render() {
// return (
// <div>
// <h5>grandchild</h5>
// <h6>名字: {this.context.name},年龄:{this.context.age}</h6>
// </div>
// )
// }
// }
function C(){
return (
<div>
<h5>grandchild</h5>
{/* <h6>名字: {this.context.name},年龄:{this.context.age}</h6> */}
<Consumer>
{
value=>{
return <h6>名字: {value.name},年龄:{value.age}</h6>
}
}
</Consumer>
</div>
)
}
export default A
render-props
动态传入带内容的结构(标签), 类似Vue中slot插槽;
如果两个组件中部分功能相同,可抽离出,复用state&操作状态的方法;可使用render-props & 高阶组件(HOC)方式
import React, { Component } from 'react'
class Count extends Component {
render() {
return (
<div>
<h5>A</h5>
<A renderC={(name)=> <B name={name}/>}/>
</div>
)
}
}
class A extends Component {
state = {name:'tom',age:10}
render() {
let {name} = this.state;
return (
<div>
<h5>B</h5>
{this.props.renderC(name)} //props.renderC将state暴露到组件外部
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<h5>C {this.props.name}</h5>
</div>
)
}
}
export default Count
import React, { Component } from 'react'
import img from '../../assets/pi.jpg'
class CC extends Component {
render() {
return (
<div>
{/* <Mouse renderFn={(mouse)=>{
return <p>鼠标位置: {mouse.x}--{mouse.y}</p>
}}/> */}
<Mouse>
{(mouse)=>{
return <p>鼠标位置: {mouse.x}--{mouse.y}</p>
}}
</Mouse>
{/* <Mouse renderFn={(mouse)=>{
return <img src={img} alt="cat" style={{position:'absolute',top:mouse.y,left:mouse.x,width:'100px'}}/>
}}/> */}
</div>
)
}
}
class Mouse extends Component {
state = {x:0, y:0};
handleMouseMove = (e)=>{
this.setState({
x: e.clientX, y: e.clientY
})
}
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
// return this.props.renderFn(this.state) //props.renderC将state暴露到组件外部
return this.props.children(this.state)
}
}
export default CC
高阶组件
是一个函数,接收要包装的组件,返回增强后的组件,高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑.
import React, { Component } from 'react'
import img from '../../assets/pi.jpg'
function withMouse(WrappedComponent){
class Mouse extends Component {
state = {x:0, y:0};
handleMouseMove = (e)=>{
this.setState({
x: e.clientX, y: e.clientY
})
}
componentDidMount(){
window.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount(){
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
console.log(this.props);
return (<WrappedComponent {...this.state} {...this.props}></WrappedComponent>)
}
}
return Mouse;
}
const Position = props => {
return (<p>鼠标位置: {props.x}--{props.y}</p>)
}
const MousePosition = withMouse(Position);
class CC extends Component {
render() {
return (
<div>
<h4>cc</h4>
{/* 渲染增强后的组件 */}
<MousePosition a={1}/>
</div>
)
}
}
export default CC
错误边界
只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误(只在生产模式起作用)
import React, { Component } from 'react'
class Parent extends Component {
state = {
hasError:'', //组件是否产生错误
}
//当Parent子组件出现报错时候,会触发getDerivedStateFromError调用,并懈怠错误信息
static getDerivedStateFromError(err){
console.log(err);
return {hasError:err} // 在render之前触发,返回新的state
}
render() {
return (
<div>
<h5>A</h5>
{this.state.hasError ? <h3>稍后再试</h3> : <Child/>}
</div>
)
}
}
class Child extends Component {
state = {
users:'no',
// users:[
// {id:1,name:'tom',age:10},
// {id:2,name:'jerry',age:20},
// ]
}
render() {
return (
<div>
<h5>Child</h5>
{
this.state.users.map(item=>{
return <h5 key={item.id}>{item.name}</h5>
})
}
</div>
)
}
}
export default Parent
组件优化
1.减轻state: 只存储与组件渲染相关的数据.(如定时器timer可放在this实例对象中);
2.组件更新机制: 父组件重新渲染时,也会重新渲染子组件树.当子组件没有任何变化时不进行重新渲染,使用钩子函数shouldComponentUpdate(nextProps,nextState);
3.使用纯组件PureComponent: PureComponent内部自动实现了shouldComponentUpdate钩子,内部通过shallow compare(数据类型-浅对比)对比前后两次props和state值,来判断是否重新渲染组件。
但是对于引用类型来说,直接修改this.state.obj.name="tom",再setState({obj:this.state.obj}),就不会重新渲染组件。
import React, { Component } from 'react'
//class index extends PureComponent {
class index extends Component {
state = {
number:0,
obj:{name:'tom',age:10},
arr:[1,2,3]
}
handleClick = ()=>{
this.setState((state)=>{
return {
number: Math.floor( Math.random() * 3),
obj: {...state.obj, age:20},
arr: [...state.arr, 4,5,6]
}
})
}
shouldComponentUpdate(nextProps, nextState){
console.log('更新值:',nextState.number,'上一次值:',this.state.number)
return nextState.number === this.state.number ? false:true;
}
render() {
console.log('render');
return (
<div>
<p>随机数: {this.state.number}</p>
<button onClick={this.handleClick}>click</button>
</div>
)
}
}
export default index
axios
npm install axios;
import React, { Component } from 'react'
import { Carousel, Flex } from 'antd-mobile';
import axios from 'axios';
import pi from '../../../assets/pi.jpg'
import './index.css'
const navs = [ //这里的数据没有放在state中,因为state是状态,一般存储的是变化的内容。
{ id:1, img: pi, title: 'txt1', path: '/home/list' },
{ id:2, img: pi, title: 'txt2', path: '/home/list' },
{ id:3, img: pi, title: 'txt3', path: '/home/list' },
]
export default class index extends Component {
state = {
swipers: [],
isSwiperLoaded: false,
}
async getSwipers(){
// 1.轮播图不自动播放? 2.从其他路由返回,高度缩短? 动态加载后swipers数量不一致
// const res = await axios.get('http://localhost:9000/home/swiper',{
// params:{ id: lorem }
//});
this.setState({
isSwiperLoaded: true,
swipers: [
'https://zos.alipayobjects.com/rmsportal/AiyWuByWklrrUDlFignR.png',
'https://zos.alipayobjects.com/rmsportal/TekJlZRVCjLFexlOCuWn.png',
'https://zos.alipayobjects.com/rmsportal/IJOtIlfsYdTyaDTRVrLI.png'],
});
}
componentDidMount() {
this.getSwipers();
}
renderSwipers(){
return this.state.swipers.map(item=>{
return <a key={item} href="http://www.alipay.com" style={{ display: 'inline-block', width: '100%', height: 176 }}>
<img alt="" src={item} style={{ width: '100%', verticalAlign: 'top' }} />
</a>
})
}
renderNavs(){
return navs.map(item=>(
<Flex.Item key={item.id} onClick={()=>{ this.props.history.push(item.path) }}>
<img src={item.img} />
<h5>{item.title}</h5>
</Flex.Item>
))
}
render() {
return (
<div>
<div className="swiper"> {/* <-设置默认高度避免axios数据回来后闪动 */}
{/* 3.通过isSwiperLoaded: true时,才渲染轮播组件 */}
{this.state.isSwiperLoaded ? (
<Carousel autoplay infinite>
{this.renderSwipers()}
{/* {this.state.swipers.map(item => (
<a key={item} href="http://www.alipay.com" style={{ display: 'inline-block', width: '100%', height: 176 }}>
<img alt="" src={item} style={{ width: '100%', verticalAlign: 'top' }} />
</a>
))} */}
</Carousel>
): '' }
</div>
<Flex className="nav">
{this.renderNavs()}
</Flex>
</div>
);
}
}
</div>
);
}
}
react-spring 动画库
install: npm install react-spring
文档: https://react-spring.io/components/spring
导入Spring组件,使用spring组件包裹要实现动画效果的遮罩层div。
通过render-props模式, 将参数props(样式)设置为遮罩层div的style。
给Spring组件添加from,to属性,组件第一次渲染时动画状态到更新的动画状态。
import React, { Component } from 'react'
import { Spring, animated } from 'react-spring'
import './index.scss'
export default class index extends Component {
state = {
show: false,
}
showBar = ()=>{
var tempShow = this.state.show;
this.setState({show: !tempShow})
}
renderAnimation = ()=>{
var tempShow = this.state.show;
//from是在初始化时; 之后的变化都是根据to属性展示隐藏
return <Spring from={{opacity: 0}} to={{opacity: tempShow? 1:0}}>
{styles => {
return <animated.div style={styles} className="animation">ccc</animated.div>
}}
</Spring>
}
render() {
return (
<div>
<button onClick={ this.showBar }>click</button>
{this.renderAnimation()}
{/* {
this.state.show ?
<Spring from={{opacity: 0}} to={{opacity: 1}}>
{styles => ( //.animation{ margin-top: 30px; background: orange; }
<animated.div style={styles} className="animation">ccc</animated.div>
)}
</Spring> : null
} */}
</div>
)
}
}
![](https://img.haomeiwen.com/i20409039/648626792fc839d6.gif)
网友评论