React 学习笔记和总结

218 阅读18分钟

前言

小弟初入前端这是小弟的学习笔记和总结,不适合学习使用;如有大佬发现错误还望指出,在此谢过。本文很多地方引用了技术胖大佬的博客文章内容

一.路由使用

1、项目中加载路由

cnpm i react-router-dom -S

2、index.js中设置路由格式

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import as serviceWorker from './serviceWorker';
//哈希路由,用这个路由地址中端口号后边有# 现在用的没有,注意下边渲染处也要更改。
//import {HashRouter} from 'react-router-dom';
import {BrowserRouter} from 'react-router-dom';

ReactDOM.render(
	<BrowserRouter>
	<App />
	</BrowserRouter>
	, document.getElementById('root'));

serviceWorker.unregister();

3、创建路由文件

在src文件夹中创建router文件夹下创建index.js文件

import React,{Component} from "react";
import {Switch,Route,Redirect}from "react-router-dom";
import Home from "../videx/home.jsx";

class RouterIndex extends Component{
    render(){
        return (
            <Switch>
                <Route path="/" exact render={()=>(<Redirect to="/home" />)} />
				<Route path="/home" component={Home}/>
            </Switch>
        );
    }
}
export default RouterIndex;

4、使用路由

在使用的地方先引入路由文件然后用标签的方式使用

import React from 'react';
import { Button } from 'antd';
import {Link } from "react-router-dom";
//路由引入
import RouterIndex from "./router/index.jsx";
import 'antd/dist/antd.css';
function App() {
  return (
  	<div>
        <li> <Link to="/">首页</Link> </li>
  		<Button type="primary">Button</Button>
          {//路由使用}
  		<RouterIndex/>
  	</div>);
}
export default App;

5、路由守卫

import React,{Component} from "react";
import {Switch,Route,Redirect}from "react-router-dom";
import Home from "../videx/home.jsx";

class RouterIndex extends Component{
    render(){
        return (
            <Switch>
                <Route path="/" exact render={()=>(<Redirect to="/home" />)} />
				<Route path="/home" component={Home}/>
                <Route path="/plage" render={(props)=>{
                    //登录校验,
                    if(!Authlogin){
                        //跳转到登录界面,记录当前界面,登录成功后返回,
                        //跳转到用户之前请求的页面
                        return <Redirect to={'/login?preurl=${props.match.path}'} />
                    }
                    return <Home{..props}></Home>
                }}/>
                <Route component={Empty}/>     {//404页面}
            </Switch>
        );
    }
}
export default RouterIndex;

二、图片的使用

1、使用相对路径

<img className="img" src={require('./img/RDS.png')} alt=""/> // 相对路径

2、使用模块引入

import icon from "./img/RDS.png";
<img src={icon} alt="" width="35" height="35" />

三、父子组件传值

1、父组件向子组件传值

父组件向子组件传值。

<XiaojiejieItem content={item} />

现在值已经顺利的传递了过去,这时候可以通过this.props.xxx的形式进行接受,比如传递过来的值,可以用如下代码进行接收。

import React, { Component } from 'react'; //imrc
class XiaojiejieItem  extends Component { //cc
    render() { 
        return ( 
            <div>{this.props.content}</div>
         );
    }
}
export default XiaojiejieItem;

注:父组件向子组件传递内容,靠属性的形式传递。

2、子组件向父组件传递数据

现在要作这样一个功能:点击组件中的菜单项后,删除改菜单项。在前边的课程中已经学习了这个知识,知识现在组件拆分了,就涉及了一个字组件向父组件传递数据的知识需要掌握。

获取数组索引下标

通过父组件传递给子组件索引下标.这里还是通过props属性的形式进行传递。

<ul>
    {
        this.state.list.map((item,index)=>{
            return (
                <XiaojiejieItem 
                key={index+item}  
                content={item}
                index={index} />
            )
        })
    }
</ul>  

现在就要通过操作子组件删除父组件里的数据了。但是React有明确规定,子组件时不能操作父组件里的数据的,所以需要借助一个父组件的方法,来修改父组件的内容。其实在以前已经写了一个删除方法deleteItem,现在要作的就是子组件调用这个方法。

//删除单项服务
deleteItem(index){
    let list = this.state.list
    list.splice(index,1)
    this.setState({
        list:list
    })

}

子组件调用父组件方法

如果子组件要调用父组件方法,其实和传递数据差不多,只要在组件调用时,把方法传递给子组件就可以了,记得这里也要进行this的绑定,如果不绑定子组件是没办法找到这个父组件的方法的。

<ul>
    {
        this.state.list.map((item,index)=>{
            return (
                <XiaojiejieItem 
                key={index+item}  
                content={item}
                index={index}
                //关键代码-------------------start
                deleteItem={this.deleteItem.bind(this)}
                //关键代码-------------------end
                />
            )
        })
    }
</ul>  

传递后,在XiaojiejieItem组件里直接hi用就可以了,代码如下:

handleClick(){
    this.props.deleteItem(this.props.index)
}

到此为止,就算是实现了子组件向父组件传值。

四、父子组件传值校验

1、需要先引入PropTypes

import PropTypes from 'prop-types'

2、配置

就可以在组件的下方进行引用了,需要注意的是子组件的最下面(不是类里边),写入下面的代码:

import React, { Component } from 'react'; //imrc
import PropTypes from 'prop-types'
class XiaojiejieItem  extends Component { //cc
   constructor(props){
       super(props)
       this.handleClick=this.handleClick.bind(this)
   }
    render() { 
        return ( 
            <div onClick={this.handleClick}>
                {this.props.content}
            </div>
        );
    }
    handleClick(){
        this.props.deleteItem(this.props.index)
    }
}
 //--------------主要代码--------start
XiaojiejieItem.propTypes={
    content:PropTypes.string,
    deleteItem:PropTypes.func,
    index:PropTypes.number
}
 //--------------主要代码--------end
export default XiaojiejieItem;

使用isRequired关键字了,它表示必须进行传递,如果不传递就报错。

avname:PropTypes.string.isRequired

3、使用默认值

defalutProps就可以实现默认值的功能,比如现在把avname的默认值设置成"松岛枫" ,然后把avname的属性删除掉。

XiaojiejieItem.defaultProps = {
    avname:'小陈'
}

五、Context后代组件传值

**React.createContext:**创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据

const {Provider, Consumer} = React.createContext(defaultValue);

Provider(生产者): 和他的名字一样。用于生产共享数据的地方。生产什么呢? 那就看value定义的是什么了。value:放置共享的数据。

<Provider value={/*共享的数据*/}>
    /*里面可以渲染对应的内容*/
</Provider>

Consumer(消费者):这个可以理解为消费者。 他是专门消费供应商(Provider 上面提到的)产生数据。Consumer需要嵌套在生产者下面。才能通过回调的方式拿到共享的数据源。当然也可以单独使用,那就只能消费到上文提到的defaultValue

<Consumer>
  {value => /*根据上下文  进行渲染相应内容*/}
</Consumer>

实例:

  1. App.js 父组件
//App.js
import React from 'react';
import Son from './son';//引入子组件
// 创建一个 theme Context,
export const {Provider,Consumer} = React.createContext("默认名称");
export default class App extends React.Component {
    render() {
        let name ="小人头"
        return (
            //Provider共享容器 接收一个name属性
            <Provider value={name}>
                <div style={{border:'1px solid red',width:'30%',margin:'50px auto',textAlign:'center'}}>
                    <p>父组件定义的值:{name}</p>
                    <Son />
                </div>
            </Provider>
        );
    }
}

son.js 子组件

//son.js 子类
import React from 'react';
import { Consumer } from "./index";//引入父组件的Consumer容器
import Grandson from "./grandson.js";//引入子组件
function Son(props) {
    return (
        //Consumer容器,可以拿到上文传递下来的name属性,并可以展示对应的值
        <Consumer>
            {( name ) =>
                <div style={{ border: '1px solid blue', width: '60%', margin: '20px auto', textAlign: 'center' }}>
                    <p>子组件。获取父组件的值:{name}</p>
                    {/* 孙组件内容 */}
                    <Grandson />
               </div>
            }
        </Consumer>
    );
}
export default Son;

grandson.js 孙组件

//grandson.js 孙类
import React from 'react';
import { Consumer } from "./index";//引入父组件的Consumer容器
function Grandson(props) {
    return (
         //Consumer容器,可以拿到上文传递下来的name属性,并可以展示对应的值
        <Consumer>
            {(name ) =>
                   <div style={{border:'1px solid green',width:'60%',margin:'50px auto',textAlign:'center'}}>
                   <p>孙组件。获取传递下来的值:{name}</p>
               </div>
            }
        </Consumer>
    );
}
export default Grandson;

六、 redux的使用

1、redux

(1)安装redux

npm install --save redux

(2)创建仓库

src目录下创建一个store文件夹,然后在文件夹下创建一个index.js文件。

index.js就是整个项目的store文件,打开文件,编写下面的代码。

import { createStore } from 'redux'  //  引入createStore方法
import reducer from './reducer'    
const store = createStore(reducer) // 创建数据存储仓库
export default store   //暴露出去

这样虽然已经建立好了仓库,但是这个仓库很混乱,这时候就需要一个有管理能力的模块出现,这就是Reducers。这两个一定要一起创建出来,这样仓库才不会出现互怼现象。在store文件夹下,新建一个文件reducer.js,然后写入下面的代码。

const defaultState = {}  //默认数据
export default (state = defaultState,action)=>{  //就是一个方法函数
    return state
}

(3)读取数据

reducer.js文件的defaultState对象中,设置默认数据

const defaultState = {
    inputValue : 'Write Something',
    list:[
        '早上4点起床,锻炼身体',
        '中午下班游泳一小时'
    ]
}
export default (state = defaultState,action)=>{
    return state
}

在需要读取的文件中使用

import React, { Component } from 'react';
import 'antd/dist/antd.css'
import { Input , Button , List } from 'antd'
import store from './store/index'
class TodoList extends Component {
constructor(props){
    super(props)
    //关键代码-----------start
    this.state=store.getState();
    //关键代码-----------end
    console.log(this.state)
}
    render() { 
        return ( 
            <div style={{margin:'10px'}}>
                <div>
                    <Input placeholder={this.state.inputValue} style={{ width:'250px', marginRight:'10px'}}/>
                    <Button type="primary">增加</Button>
                </div>
                <div style={{margin:'10px',width:'300px'}}>
                    <List
                        bordered
                        //关键代码-----------start
                        dataSource={this.state.list}
                        //关键代码-----------end
                        renderItem={item=>(<List.Item>{item}</List.Item>)}
                    />    
                </div>
            </div>
         );
    }
}
export default TodoList;

(4)设置数据

使用的文件夹中设置事件,在事件中设置action发送数据

import React, {Component} from "react";
import { Input , Button , List} from 'antd';
import 'antd/dist/antd.css';
import store from '.././store/index'
class Home extends Component {
    constructor(props){
        super(props)
        this.state=store.getState();
        this.changeInputValue= this.changeInputValue.bind(this)
        this.storeChange = this.storeChange.bind(this)  //转变this指向
        store.subscribe(this.storeChange) //订阅Redux的状态使页面数据更新
        this.clickBtn = this.clickBtn.bind(this)
    }
    render(){
         return (
         	<div>
         	   <Input placeholder={this.state.inputValue} style={{ width:'250px', marginRight:'10px'}} onChange={this.changeInputValue} value={this.state.inputValue}/>
                <Button type="primary" onClick={this.clickBtn}>增加</Button>
                <List
                        bordered
                        dataSource={this.state.list}
                        renderItem={item=>(<List.Item>{item}</List.Item>)}
                />  
            </div>
        )
    }
    changeInputValue(e){
        // 创建action
        const action ={
            type:'changeInput',
            value:e.target.value
        }
        // 发送数据
        store.dispatch(action)
    }
    // 订阅事件
    storeChange(){
     this.setState(store.getState())
    }
    clickBtn(){
        const action = { type:'addItem'}
        store.dispatch(action)
    }
}

export default Home;

Reducer接收并改变store里的值。(记住:Reducer里只能接收state,不能改变state。),所以我们声明了一个新变量,然后再次用return返回回去。

const defaultState = {
	inputValue : 'Write Something',
    list:[
        '早上4点起床,锻炼身体',
        '中午下班游泳一小时'
    ]
}  //默认数据
export default (state = defaultState,action)=>{  //就是一个方法函数
	// 通过判断接收新值
	if(action.type === 'changeInput'){
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.inputValue = action.value
        return newState
    }
    if(action.type === 'addItem' ){ //根据type值,编写业务逻辑
        let newState = JSON.parse(JSON.stringify(state)) 
        newState.list.push(newState.inputValue)  //push新的内容到列表中去
        newState.inputValue = ''
        return newState
    }
    return state
}

(5)优化

Redux Action的时候,我们写了很多Action的派发,产生了很多Action Types,如果需要Action的地方我们就自己命名一个Type,会出现两个基本问题:

  • 这些Types如果不统一管理,不利于大型项目的服用,设置会长生冗余代码。
  • 因为Action里的Type,一定要和Reducer里的type一一对应在,所以这部分代码或字母写错后,浏览器里并没有明确的报错,这给调试带来了极大的困难。

那我司中会把Action Type单独拆分出一个文件。在src/store文件夹下面,新建立一个actionTypes.js文件,然后把Type集中放到文件中进行管理。

export const  CHANGE_INPUT = 'changeInput'
export const  ADD_ITEM = 'addItem'
export const  DELETE_ITEM = 'deleteItem'

引入Action中并使用

写好了ationType.js文件,可以引入到TodoList.js组件当中,引入代码如下:

import { CHANGE_INPUT , ADD_ITEM , DELETE_ITEM } from './store/actionTypes'

引入后可以在下面的代码中进行使用这些常量代替原来的Type值了.

changeInputValue(e){
    const action ={
        type:CHANGE_INPUT,
        value:e.target.value
    }
    store.dispatch(action)
}
clickBtn(){
    const action = { type:ADD_ITEM }
    store.dispatch(action)
}
deleteItem(index){
    const action = {  type:DELETE_ITEM, index}
    store.dispatch(action)
}

引入Reducer并进行更改

也是先引入actionType.js文件,然后把对应的字符串换成常量,整个代码如下:

import {CHANGE_INPUT,ADD_ITEM,DELETE_ITEM} from './actionTypes'
const defaultState = {
    inputValue : 'Write Something',
    list:[
        '早上4点起床,锻炼身体',
        '中午下班游泳一小时'
    ]
}
export default (state = defaultState,action)=>{
    if(action.type === CHANGE_INPUT){
        let newState = JSON.parse(JSON.stringify(state)) //深度拷贝state
        newState.inputValue = action.value
        return newState
    }
    //state值只能传递,不能使用
    if(action.type === ADD_ITEM ){ //根据type值,编写业务逻辑
        let newState = JSON.parse(JSON.stringify(state)) 
        newState.list.push(newState.inputValue)  //push新的内容到列表中去
        newState.inputValue = ''
        return newState
    }
    if(action.type === DELETE_ITEM ){ //根据type值,编写业务逻辑
        let newState = JSON.parse(JSON.stringify(state)) 
        newState.list.splice(action.index,1)  //push新的内容到列表中去
        return newState
    }
    return state
}

编写actionCreators.js文件

/src/store文件夹下面,建立一个新的文件actionCreators.js,先在文件中引入上节课编写actionTypes.js文件。

import {CHANGE_INPUT}  from './actionTypes'

引入后可以用const声明一个changeInputAction变量,变量是一个箭头函数,代码如下:

import {CHANGE_INPUT}  from './actionTypes'
export const changeInputAction = (value)=>({
    type:CHANGE_INPUT,
    value
})

修改todoList中的代码

有了文件,就可以把actionCreatores.js引入到TodoLisit中。

import {changeInputAction} from './store/actionCreatores'

引入后,可以把changeInputValue()方法,修改为下面的样子。

changeInputValue(e){
        const action = changeInputAction(e.target.value)
        store.dispatch(action)
    }

然后再浏览器中打开程序,进行测试,也是完全正常的。

2、中间件Redux-thunk组件

(1) 安装

npm install --save redux-thunk

(2)配置

引入applyMiddleware,如果你要使用中间件,就必须在redux中引入applyMiddleware.

import { createStore , applyMiddleware } from 'redux' 

引入redux-thunk

import thunk from 'redux-thunk'

如果你按照官方文档来写,你直接把thunk放到createStore里的第二个参数就可以了,但以前我们配置了Redux Dev Tools,已经占用了第二个参数。

官方文档给的方法:

const store = createStore(
    reducer,
    applyMiddleware(thunk)
) // 创建数据存储仓库

这样写是完全没有问题的,但是我们的Redux Dev Tools插件就不能使用了,如果想两个同时使用,需要使用增强函数。使用增加函数前需要先引入compose

import { createStore , applyMiddleware ,compose } from 'redux' 

然后利用compose创造一个增强函数,就相当于建立了一个链式函数,代码如下:

const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose

有了增强函数后,就可以把thunk加入进来了,这样两个函数就都会执行了。

const enhancer = composeEnhancers(applyMiddleware(thunk))

这时候直接在createStore函数中的第二个参数,使用这个enhancer变量就可以了,相当于两个函数都执行了。

const store = createStore( reducer, enhancer) // 创建数据存储仓库

也许你对增加函数还不能完全理解,其实你完全把这个想成固定代码,直接使用就好,我在这里给出全部代码,方便你以后学习使用。

import { createStore , applyMiddleware ,compose } from 'redux'  //  引入createStore方法
import reducer from './reducer'    
import thunk from 'redux-thunk'
const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose
const enhancer = composeEnhancers(applyMiddleware(thunk))
const store = createStore( reducer, enhancer) // 创建数据存储仓库
export default store   //暴露出去

(3)使用

在优化是将所有的action都抽离成一个actionCreators.js文件,例如我们需要将axios请求来的数据给redux做默认值可以写一个这样的方法在actionCreators.js文件中

export const getTodoList = () =>{
    return (dispatch)=>{
        axios.get('https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList').then((res)=>{
            const data = res.data
            const action = getListAction(data)//actionCreators.js(此文件)文件中定义的方法。
            dispatch(action)
        })
    }
}

使用上面的方法

import {getTodoList} from './store/actionCreatores'//引入方法
//代码省略
componentDidMount(){
        const action = getTodoList()
        store.dispatch(action)
}

3、React-redux的使用

(1)安装

直接使用npm在命令行安装React-redux

npm install --save react-redux

还要安装redux,安装好后要创建一个store文件夹,在/store下创建一个index.js文件,并写入下面代码:

import {createStore} from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store

目前我们还没有reducer,所以我们要创建reducer.js文件,代码如下:

const defalutState = {
    inputValue : 'jspang',
    list :[]
}
export default (state = defalutState,action) =>{
    return state
}

(2)使用

<Provider>提供器

只要使用了这个组件,组件里边的其它所有组件都可以使用store了,这也是React-redux的核心组件了。有了<Provider>就可以把/src/index.js改写成下面的代码样式:用<Provider>将APP组件包裹

import React from 'react';
import ReactDOM from 'react-dom';
import TodoList from './TodoList'
//---------关键代码--------start
import { Provider } from 'react-redux'
import store from './store'
//声明一个App组件,然后这个组件用Provider进行包裹。
const App = (
    <Provider store={store}>
        <TodoList />
    </Provider>
)
//---------关键代码--------end
ReactDOM.render(App, document.getElementById('root'));

connect连接器

在使用数据的文件中使用

import React, { Component } from 'react';
import store from './store'
import {connect} from 'react-redux'
class TodoList extends Component {
    constructor(props){
        super(props)
        this.state = store.getState()
    }
    render() { 
        return (
            <div>
                <div>
                    {/*value=...接收使用数据;onChange=...修改数据方法*/}
                    <input value={this.props.inputValue} 
                        onChange={this.props.inputChange} />
                    <button>提交</button>
                </div>
                <ul>
                    <li>JSPang</li>
                </ul>
            </div>
            );
    }
}
//接收数据
const stateToProps = (state)=>{
    return {
        inputValue : state.inputValue
    }
}
//修改数据方法
const dispatchToProps = (dispatch) =>{
    return {
        inputChange(e){
            let action = {
                type:'change_input',
                value:e.target.value
            }
            dispatch(action)
        }
    }
}
export default connect(stateToProps,dispatchToProps)(TodoList);

需求在store文件夹下的reducer.js里边,编写对应的业务逻辑了。

const defalutState = {
    inputValue : 'jspang',
    list :[]
}
export default (state = defalutState,action) =>{
    if(action.type === 'change_input'){
        let newState = JSON.parse(JSON.stringify(state))
        newState.inputValue = action.value
        return newState
    }
    return state
}

七、create-react-app解决跨域问题

下载 http-proxy-middleware

npm i http-proxy-middleware --save

安装路由
npm install -save axios

创建 src/setupProxy.js

const proxy = require('http-proxy-middleware')
module.exports = function(app) {
  // /api 表示代理路径
  // target 表示目标服务器的地址
  app.use(proxy.createProxyMiddleware(
    '/api', {
      target: 'http://localhost:4000',// http://localhost:4000/ 地址是示例,实际地址以项目为准
      changeOrigin: true,// 跨域时一般都设置该值 为 true
      pathRewrite: {  // 重写接口路由
        '^/api': '' // 这样处理后,最终得到的接口路径为: http://localhost:8080/xxx
      }
    })
  )
  app.use(proxy.createProxyMiddleware(
		"/关键词",{
			target:"目标域名地址",
			changeOrigin:true,
			pathRewrite:{
				"^/关键词":""
			}
		}
	))
}

使用

import React, {Component} from "react";
import axios from 'axios'
class App extends Component {
    render(){
         return (
         	<div>123</div>
        )}
    componentDidMount(){ //页面加载时使用
        axios.get('/api/students')
            .then((res)=>{console.log(res)  })
            .catch((error)=>{console.log('axios 获取数据失败'+error)})
    } 
}
export default App;

八、生命周期函数

组件将要挂载时触发的函数:componentWillMount 即将被废弃17版本之后必须加上 UNSAFE_ 才可以工作(UNSAFE_componentWillMount)

组件挂载完成时触发的函数:componentDidMount

是否要更新数据时触发的函数:shouldComponentUpdate

将要更新数据时触发的函数:componentWillUpdate即将被废弃新的生命周期—— getSnapshotBeforeUpdate 替换

数据更新完成时触发的函数:componentDidUpdate

组件将要销毁时触发的函数:componentWillUnmount

父组件中改变了props传值时触发的函数:componentWillReceiveProps即将被废弃,不建议使用,17版本之后必须加上 UNSAFE_ 才可以工作(UNSAFE_componentWillReceiveProps)新的生命周期替换—— getDerivedStateFromProps

九、优化

组件优化

我们通过重写 shouldComponentUpdate 方法来控制子组件的渲染,当该方法返回 false 会阻止组件的渲染。

shouldComponentUpdate(nextProps,nextState){
    return this.props !== nextProps
    && this.state !== nextState
}

十、React Hooks

1.useContext父子组件传值

父组件中通过引用的createContext声明并向外暴露CountContext,在标签CountContext.Provider包裹的组件都能接收到

import React, { useState , createContext } from 'react';
//===关键代码
export const CountContext = createContext()
function Example4(){
    const [ count , setCount ] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
            {/*======关键代码 通过value传值*/}
            <CountContext.Provider value={count}>
            </CountContext.Provider>
        </div>
    )
}
export default Example4;

子组件使用useContext就可以接收,但是在使用前需要新进行引入useContext(不引入是没办法使用的)。

//引用
import React,{useContext} from 'react';
//接受父组件暴露的值
import {ColorContext} from './color.jsx'
function ShowArea (){
    //接收
	const {color}=useContext(ColorContext)
	return (
		<div style={{color:color}}>字体颜色为{color}</div>
	)
}
export default ShowArea

2 useReducer的使用

import React, { useReducer } from 'react';
function ReducerDemo(){
    const [ count , dispatch ] =useReducer((state,action)=>{
        switch(action){
            case 'add':
                return state+1
            case 'sub':
                return state-1
            default:
                return state
        }
    },0)
    return (
       <div>
           <h2>现在的分数是{count}</h2>
           <button onClick={()=>dispatch('add')}>Increment</button>
           <button onClick={()=>dispatch('sub')}>Decrement</button>
       </div>
    )

}
export default ReducerDemo

3. useContext和useReducer结合使用实现redux

总文件,引入三个组件并将其按对应的格式书写出来

import React, { useReducer } from 'react';
import ShowArea from './ShowArea';
import Buttons from './Buttons';
import { Color } from './color';   //引入Color组件因为它使用的export暴露的不是export default,所有要用{}引入

function Example6(){
    return (
        <div>
            <Color>
                <ShowArea />
                <Buttons />
            </Color>

        </div>
    )
}
export default Example6

Color组件

import React, { createContext,useReducer } from 'react';
export const ColorContext = createContext({})
export const UPDATE_COLOR = "UPDATE_COLOR"
const reducer =(state,action)=>{
	switch(action.type){
        case UPDATE_COLOR:
            return action.color
        default:
            return state
    }
}
//因为上方还向外暴露了ColorContext 和UPDATE_COLOR所以这里也要使用export向外暴露,一个组件暴露了多个属性。
export const Color = props=>{
	const [color,dispatch]=useReducer(reducer,'blue')
    return (
        <ColorContext.Provider value={{color,dispatch}}>
            {props.children}   {/*在Example6组件中本组件中的子组件这是插槽,在函数声明(箭头函数)时传入了props*/}
        </ColorContext.Provider>
    )
}

接收color使用

import React,{useContext} from 'react';
import {ColorContext} from './color.jsx'
function ShowArea (){
	const {color}=useContext(ColorContext)
	return (
		<div style={{color:color}}>字体颜色为{color}</div>
	)
}
export default ShowArea

接收dispatch修改颜色

import React ,{useContext} from 'react';
import {ColorContext,UPDATE_COLOR} from './color'
function Buttons(){
    const { dispatch } = useContext(ColorContext)
    return (
        <div>
            <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"red"})}}>红色</button>
            <button onClick={()=>{dispatch({type:UPDATE_COLOR,color:"yellow"})}}>黄色</button>
        </div>
    )
}
export default Buttons

4.useEffect代替生命周期函数

function Index() {
    useEffect(()=>{
        console.log('useEffect=>老弟你来了!Index页面')
        return ()=>{
            console.log('老弟,你走了!Index页面')
        }
    },[])
    return <h2>JSPang.com</h2>;
}

当组件初次渲染(组件将要挂载时触发的函数:componentWillMount)或者数据更新(将要更新数据时触发的函数:componentWillUpdate)时执行 console.log('useEffect=>老弟你来了!Index页面')

useEffect的第二个参数为空数组**[]时,当组件初次渲染或将被销毁时才进行解绑,才执行useEffect函数,当数组[]中有参数时,参数发生变化都进行解绑,都会执行useEffect函数,当不加第二个参数时没有[]就表示每一次渲染都执行,useEffect函数可以有多个**

useEffect 里面使用到的state的值, 固定在了useEffect内部, 不会被改变,除非useEffect刷新,重新固定state的值

 const [count, setCount] = useState(0)
    useEffect(() => {
        console.log('use effect...',count)
        const timer = setInterval(() => {
            console.log('timer...count:', count)
            setCount(count + 1)
        }, 1000)
        return ()=> clearInterval(timer)
    },[])

useRef的一个作用就是相当于全局作用域,一处被修改,其他地方全更新...

  1. 就是相当于全局作用域,一处被修改,其他地方全更新...
 const [count, setCount] = useState(0)
 const countRef = useRef(0)
useEffect(() => {
    console.log('use effect...',count)
    const timer = setInterval(() => {
        console.log('timer...count:', countRef.current)
        setCount(++countRef.current)
    }, 1000)
    return ()=> clearInterval(timer)
},[])
  1. 普遍操作,用来操作dom

5.useMemo性能优化

在父子组件中父组件中数据状态改变时,子组件也会跟着重新渲染,子组件中所有方法也会都重新执行一遍,当子组件中某个方法要求性能较高时,每次还都执行,这就是性能的损耗。

function ChildComponent({name,children}){
    function changeXiaohong(name){
        console.log('她来了,她来了。小红向我们走来了')
        return name+',小红向我们走来了'
    }
    const actionXiaohong = useMemo(()=>changeXiaohong(name),[name]) //当name改变时才会执行changeXiaohong函数,,传递给子组件中的参数那个会影响到函数执行的结果,数组中写什么参数。
    return (
        <>
            <div>{actionXiaohong}</div>
            <div>{children}</div>
        </>
    )
}

加上useMemo后组件在渲染时,因name参数没有改变,其他参数改变不影响函数的结果,所有组件虽然重新渲染但是函数不在执行。

6.useCallback性能优化

在父组件中给子组件传递一个方法是,父组件重新渲染时方法重新生成了一个onChange函数,赋值给了Child组件,浅比较失败,Child组件成功重新render,尽管Child组件什么都没有做!当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。也就是说父组件传递一个函数给子组件的时候,由于父组件的更新会导致该函数重新生成从而传递给子组件的函数引用发生了变化,这就会导致子组件也会更新,而很多时候子组件的更新是没必要的,所以我们可以通过useCallback来缓存该函数,然后传递给子组件。

const Child = memo(({data, onChange}) =>{
    console.log('child render...')
    return (
        <div>
            <div>child</div>
            <div>{data}</div>
            <input type="text" onChange={onChange}/>
        </div>
    );
})
const Hook =()=>{
    console.log('Hook render...')
    const [count, setCount] = useState(0)
    const [name, setName] = useState('rose')
    const [text, setText] = useState('')

   const onChange=(e)=>{
        setText(e.target.value)
   }
    return(
        <div>
            <div>count: {count}</div>
            <div>text : {text}</div>
            <button onClick={()=>setCount(count + 1)}>count + 1</button>
            <Child data={name} onChange={onChange}/>
        </div>
    )
}

如何使用useCallback?

   const onChange = useCallback((e)=>{
        setText(e.target.value)
   },[])

useMemo和useCallback的区别

useMemouseCallback接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于useMemo返回的是函数运行的结果,useCallback返回的是函数。

1.useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态 2.useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。

样式

class样式

import React, { Component } from 'react';

class Example extends Component {
    constructor(props) {
        super(props);
        this.state = { count:0 }
    }
    render() { 
        return (
            <div>
                <p>You clicked {this.state.count} times</p>
                <button onClick={this.addCount.bind(this)}>Chlick me</button>
            </div>
        );
    }
    addCount(){
        this.setState({count:this.state.count+1})
    }
}

export default Example;

hook

import React, { useState } from 'react';
function Example(){
    const [ count , setCount ] = useState(0);
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={()=>{setCount(count+1)}}>click me</button>
        </div>
    )
}
export default Example;