脚手架
- 安装脚手架:npm install -g create-react-app
- 修改镜像源:npm config set registry registry.npm.taobao.org
- 创建项目:npx create-react-app my-app
- 进入到目录:cd my-app
- 启动项⽬:npm start
- 暴露配置项:npm run eject
初识react
1.将src下的所有文件都删除,
2.将public文件下除列favicon.ico和index.html之外的文件都删除掉
修改index.html文件
3.我们需要删除选中的内容
4.这两行内容是我们之前引入的一个图标和manifest文件
5.在src目录下,
6.在index.js中开始编写React代码
import React from "react";
import ReactDOM from 'react-dom';
ReactDOM.render(<h2>Hello React</h2>, document.getElementById("root"));
7.如果我们不希望直接在 ReactDOM.render 中编写过多的代码,就可以单独抽取一个组件App.js
import React, { Component } from 'react';
export default class App extends Component {
render() {
return <h2>Hello App</h2>
}
}
8.在index.js中引入App,并且使用它
import React from "react";
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App/>, document.getElementById("root"));
核心基础
1.基础结构
2.两个入口
3.安装vscode插件
可愉快的使用rcc rfc imp等快捷键…
分别用来创建类组件,函数式组件,和导入文件
4.jsx基础语法
import React from 'react';
import ReactDOM from 'react-dom';
// import App from './App';
const jsx = <div>hello world</div>
ReactDOM.render(jsx,document.querySelector('#root'))
5.{}
6.对象
7.条件语句
8.数组
9.属性的使⽤
组件
- 函数式组件
export function About(){
....
}
- 类组件(常用)
一个登录界面,包含获取antd输入框值和路由跳转的小功能
import React,{Component} from 'react';
import logo from '../images/1.png'
import '../assets/login.css'
import {Button,Form, Input } from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons';
export default class Login extends Component{
constructor(props){
super(props)
this.state = {
username:'',
password:''
}
}
handleSubmit = (obj) => {
// react中的跳转页面的方法
this.props.history.replace('/admin')
};
handleChange = (name,val) => {
this.setState({
[name]:val
})
};
render(){
return (
<div>
<header>
<img src={logo}/>
<span>后台管理系统</span>
</header>
<div className='loginContainer'>
<h2>立即登录</h2>
<Form
onFinish={(values) => this.handleSubmit(values)}
>
<Form.Item>
<Input prefix={<UserOutlined/>} placeholder="用户名" onChange={val => this.handleChange('username',val)}/>
</Form.Item>
<Form.Item>
<Input
prefix={<LockOutlined/>}
type="password"
placeholder="密码"
onChange={val => this.handleChange('password',val)}
/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">登录</Button>
</Form.Item>
</Form>
</div>
</div>
)
}
}
事件处理(this问题的三大方案)
1.bind(this)
2.定义方法后使用箭头函数
3.定义方法时使用箭头函数
生命周期
通信
父子传递数据
class App extends React.Component{
constructor(props){
super(props)
this.state = {
todos:['美术','数学'],
}
this.addTodo = this.addTodo.bind(this)
}
addTodo(todo){
const {todos} = this.state;
todos.unshift(todo)
this.setState({todos})
}
render(){
const {todos} = this.state;
return (
<div>
<h1>Simple todo list</h1>
//父向子传递数据(函数也可看作数据)
<Add addTodo={this.addTodo}/>
<List todos={todos}/>
</div>
)
}
}
class Add extends React.Component{
constructor(props){
super(props);
this.state = {
value:''
}
}
handleAdd = () => {
const {addInput} = this.refs;
// 子向父传递数据
this.props.addTodo(addInput.value);
// 清空输入框内容
addInput.value = '';
}
render(){
return (
<div>
<input type="text" ref='addInput'/>
<button onClick={this.handleAdd}>add</button>
</div>
)
}
}
class List extends React.Component{
constructor(props){
super(props)
}
render(){
const {todos} = this.props;
return (
<ul>
{todos.map((todo,index) => <li key={index}>{todo}</li>)}
</ul>
)
}
}
ReactDOM.render(<App />,document.querySelector('#app'))
setState
- setState在生命周期中是异步的
- setState在合成事件是异步的
- 在this.setState(object,callback)中的回调函数中可以是同步的
- setState在setTimeout中是同步的
只需要关注打印的结果即可
class State extends React.Component{
constructor(props){
super(props)
this.state = {
counter:0
}
}
// componentDidMount(){
// setState在生命周期中是异步的
// this.changeValue(1)
// }
setCounter = () => {
// setState在复合事件中是异步的
// this.changeValue(1)
// setState在定时器中是同步的
setTimeout(() => {
this.changeValue(1)
}, 0);
}
changeValue = v => {
// this.setState(object,callback)中是同步的
// this.setState({
// counter:this.state.counter + v
// },()=>{
// console.log(this.state.counter);
// })
this.setState({
counter:this.state.counter + v
})
console.log(this.state.counter);
}
render(){
return (
<div>
<button onClick={this.setCounter}>异步</button>
</div>
)
}
}
ReactDOM.render(<State/>,document.querySelector('#root'))
为什么设计setState为异步
显著提升性能
1.如果每次调用setState都进行一次更新,那么意味着render函数或被频繁调用,界面重新渲染,效率较低。
2.最好的方法应该是获取多个更新的操作,之后进行批量更新。
如果同步更新了state,但是还没有执行render函数,那么stste和props不能保持同步,会在开发中产生很多问题,如父组件的state改变了而render还未完成导致子组件props的值未能同步。
react更新机制
- props/state改变
- render函数重新执行
- 生产新的虚拟DOM树
- 新旧虚拟DOM树进行Diff
- 计算出差异进行更新
- 更新到真实的DOM
1.同层节点之间相互比较
2.不同类型的节点,产生不同的树结构然后直接删除替换
3.同一类型节点:className修改只会更新className,style中某个属性修改只会更新某个属性
4.同类型组件:更新该组件的Props,调用组件render函数,继续Diff递归比较
5.开发中,可以通过key来指定哪些节点在不同渲染下保持稳定
6.对于列表修改数据往最后添加一条数据,最后的数据会生成一个mutation,最终将其插入DOM树即可。此时添加key意义不是很大
7.对于列表修改数据往前面或中间添加一条数据,React会对每一个子元素产生一个mutation,此时key的意义就重大了,存在key时会进行key的匹配,会产生位移的操作,可以减少不必要的更新
组件优化
先看一下代码
每次点击按钮 23 和39行全部打印了
class A extends React.Component{
constructor(props){
super(props)
this.state = {
name:'小明'
}
}
render(){
console.log('A组件RENDER');
return (
<div>
<p>我是A组件</p>
<p>{this.state.name} </p>
<button onClick={this.changeName}>修改</button>
<B></B>
</div>
)
}
changeName = () => {
this.setState({}) //徐晃一枪 发现23 和39行全部打印了
}
}
class B extends React.Component{
render(){
console.log('B组件RENDER');
return (
<div>
我是B组件
</div>
)
}
}
ReactDOM.render(<A />,document.querySelector('#app'))
两个问题
- 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
- 只要当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
出现问题的原因就是上面react更新机制第四点提到的,当执行了setState(),就会执行render,render时发现有组件执行组件的render,导致不必要的更新
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
方案一
- 重写shouldComponentUpdate()方法
- 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false,如果state数据多的话这个方案是不可行的
每次点击按钮 由于shouldComponentUpdate返回的是false,就不会执行更新操作,进而不会执行render方法
class A extends React.Component{
constructor(props){
super(props)
this.state = {
name:'小明'
}
}
shouldComponentUpdate(nextProps,nextState){
// console.log(nextProps,nextState);
return !(this.state.name == nextState.name);
}
render(){
console.log('A组件RENDER');
return (
<div>
<p>我是A组件</p>
<p>{this.state.name} </p>
<button onClick={this.changeName}>修改</button>
<B name={this.state.name}></B>
</div>
)
}
changeName = () => {
this.setState({})
}
}
class B extends React.Component{
shouldComponentUpdate(nextProps,nextState){
// console.log(nextProps,nextState);
return !(this.props.name == nextProps.name);
}
render(){
console.log('B组件RENDER');
return (
<div>
<p>我是B组件</p>
<p>{this.props.name}</p>
</div>
)
}
}
ReactDOM.render(<A />,document.querySelector('#app'))
附上一段源码
方案二
1.使用PureComponent
2. PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
3. 解决不了函数式组件(使用memo解决)
注意:
- 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
- 不要直接修改state数据, 而是要产生新数据
class A extends React.PureComponent{
constructor(props){
super(props)
this.state = {
name:'小明'
}
}
render(){
console.log('A组件RENDER');
return (
<div>
<p>我是A组件</p>
<p>{this.state.name} </p>
<button onClick={this.changeName}>修改</button>
<B name={this.state.name}></B>
</div>
)
}
changeName = () => {
//会触发两个组件的render方法
this.setState({
name:'黄杰'
})
}
}
class B extends React.PureComponent{
render(){
console.log('B组件RENDER');
return (
<div>
<p>我是B组件</p>
<p>{this.props.name}</p>
</div>
)
}
}
ReactDOM.render(<A />,document.querySelector('#app'))
class A extends React.PureComponent{
constructor(props){
super(props)
this.state = {
name:'小明'
}
}
render(){
console.log('A组件RENDER');
return (
<div>
<p>我是A组件</p>
<p>{this.state.name} </p>
<button onClick={this.changeName}>修改</button>
<B name={this.state.name}></B>
</div>
)
}
changeName = () => {
//无效,PureComponent是浅比较 必须修改的是一个新的地址 而不是旧的引用地址
const obj = this.state;
obj.name = '黄杰'
this.setState(obj)
}
}
class B extends React.PureComponent{
render(){
console.log('B组件RENDER');
return (
<div>
<p>我是B组件</p>
<p>{this.props.name}</p>
</div>
)
}
}
ReactDOM.render(<A />,document.querySelector('#app'))
函数式组件的优化-memo
每次点击按钮 A组件的render方法依旧会执行 但是B组件不会执行 如果是普通函数组件 B组件render是会执行的
class A extends React.Component{
constructor(props){
super(props)
this.state = {
name:'小明'
}
}
render(){
console.log('A组件RENDER');
return (
<div>
<p>我是A组件</p>
<p>{this.state.name} </p>
<button onClick={this.changeName}>修改</button>
<B name={this.state.name}></B>
</div>
)
}
changeName = () => {
this.setState({})
}
}
const B = React.memo((props) => {
console.log('B组件render');
return (
<div>
<p>我是B组件</p>
<p>{props.name}</p>
</div>
)
})
ReactDOM.render(<A />,document.querySelector('#app'))
使用context解决爷爷向孙子传递数据的问题
两个地方要注意:
- 使用context后必须使用PropTypes进行强校验
- 爷爷要使用childContextTypes对象和getChildContext()函数
- 孙子要使用contextTypes上下文type来获取数据
import React from "react";
import PropTypes from 'prop-types';
class SideBar extends React.Component{
render(){
return (
<div>
<p>侧边栏</p>
<Navbar></Navbar>
</div>
)
}
}
class Navbar extends React.Component{
static contextTypes = {
obj:PropTypes.object.isRequired
}
render(){
return (
<div>{this.context.obj.name}的侧边栏</div>
)
}
}
export class Page extends React.Component{
constructor(props){
super(props)
this.state = {
name:'蜗牛'
}
}
static childContextTypes = {
obj:PropTypes.object.isRequired
}
getChildContext(){
return {obj:this.state}
}
render(){
return (
<div>
我是一个{this.state.name}
<SideBar/>
</div>
)
}
}
手写一个简单的redux
./redux.js
// 手写redux源码 最简代码
/**
* redux中几个重要的组成 getState subscribe dispatch
*/
export function createStore(reducer){
let currentState = {};
let currentListener = [];
function getState(){
return currentState;
}
function subscribe(listen){
currentListener.push(listen);
}
function dispatch(action){
currentState =- reducer(currentState,action);
currentListener.forEach(v => v());
return action;
}
// 初始化时执行一次得到initState 因为该type匹配不到
dispatch({type:'@IMOOC/WONIU-REDUX'})
return {getState,subscribe,dispatch}
}
./reducer.js
import {createStore} from './redux';
// 这就是reducer处理函数,参数是状态和新的action
function counter(state=0, action) {
console.log(state, action)
switch (action.type) {
case '加机关枪':
return state + 1
case '减机关枪':
return state - 1
default:
return 100
}
}
// 新建保险箱
const store = createStore(counter)
const init = store.getState()
console.log(`一开始有机枪${init}把`)
function listener(){
const current = store.getState()
console.log(`现在有机枪${current}把`)
}
// 订阅,每次state修改,都会执行listener
store.subscribe(listener)
// 提交状态变更的申请
store.dispatch({ type: '加机关枪' })
store.dispatch({ type: '加机关枪' })
store.dispatch({ type: '加机关枪' })
store.dispatch({ type: '减机关枪' })
store.dispatch({ type: '减机关枪' })
react-redux使用
如果要使用装饰器模式@符号,
首先要执行命令 npm i eject来暴漏一些文件,然后再package.json文件中的babel栏目配置:
"plugins":[
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
/index.js文件
import React from "react";
import ReactDOM from "react-dom";
import App from './App';
import { createStore,applyMiddleware } from "redux";
import {counter} from "./index.redux";
// 处理异步操作
import thunk from "redux-thunk";
import { Provider } from "react-redux";
const store = createStore(counter,applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>,
document.querySelector('#root')
);
/index.redux.js 抽离出来的redux文件
//actionType
const ADD = 'ADD'
const SUB = 'SUB'
export function counter(state = 0,action){
switch (action.type) {
case ADD:
console.log(action);
return state+1;
case SUB:
console.log(action);
return state-1;
default:
return 10;
}
}
// actionCreator
export function add(){
return {type:ADD}
}
export function sub(){
return {type:SUB}
}
// 处理异步请求必须使用react-thunk
export function addAsync(){
return dispatch => {
setTimeout(() => {
dispatch(add())
}, 1000);
}
}
/app.js文件中,需要用到store的组件中
import React from "react";
import { connect } from "react-redux";
import {add,sub,addAsync} from './index.redux'
@connect(
state => ({num:state}),
{add,sub,addAsync}
)
class App extends React.Component{
render (){
let add = this.props.add;
let sub = this.props.sub;
let addAsync = this.props.addAsync;
let num = this.props.num;
return (
<div>
<h2>现在的数字是{num}</h2>
<button onClick={()=>add()}>加1</button>
<button onClick={()=>sub()}>减1</button>
<button onClick={()=>addAsync()}>1秒会加1</button>
</div>
)
}
}
export default App;
react-router-dom使用
最好使用以下版本 @^5.3.0 最新版Redirect标签不可用
Route是搭配path和component使用
Link,Redirect是搭配to使用
<BrowserRouter>
<Switch>
<Route path='/login' component={Auth}></Route>
<Route path='/dashboard' component={Dashboard}></Route>
<Redirect to='/dashboard'></Redirect>
</Switch>
</BrowserRouter>
<div>
<ul>
<li><Link to='/dashboard/'>一营</Link></li>
<li><Link to='/dashboard/erying'>二营</Link></li>
<li><Link to='/dashboard/qibinglian'>骑兵连</Link></li>
</ul>
<Switch>
//但路由最后是以/结尾时,一般要加上exact配合使用
<Route path='/dashboard/' exact component={App}></Route>
<Route path='/dashboard/erying' component={Erying}></Route>
<Route path='/dashboard/qibinglian' component={Qibinglian}></Route>
</Switch>
</div>
临时想到几个点:
- 当组件不是路由组件时,想得到路由信息,要引入withRouter 从’react-router-dom’引入
- 子路由获取方式为this.props.match.params.user
<Route path='/chat/:user' component={Chat}></Route>