react学习第三篇:state,hooks,context

2021-05-11  本文已影响0人  云鹤道人张业斌

一、props和state的区别

  1. props是对外的接口(组件间传递数据),state是对内的接口(组件内传递数据)

2.state是私有的,可以认为是组件的“私有属性”,使用setState方法修改state

this.setState({
              isOpen: !this.state.isOpen,
            });
  1. 构建函数constructor是唯一可以初始化state的地方
  constructor(props: Props) {
    // 调用React.Component基础类的构建函数
    super(props);
    this.state = {
      isOpen: false,
    };
  }

4.setState 是同步还是异步? 同步的
setState是同步执行,异步更新,react会优化真正修改时机,可能会合并多个修改后再渲染
tip:异步更新是一种结果的表现形式,是因为setState的值被缓存了。执行还是同步的

state处理发生在生命周期变化的时候
count : 0
1.
setState后count未变化
<button onClick={() => {
          this.setState({count: this.state.count + 1})
        console.log(this.state.count)   // => 0
         }}>{this.state.count}加一</button>
2. 
setState第二参数:函数中可以获取改变后的值
<button onClick={() => {
          this.setState({count: this.state.count + 1}, () => {
            console.log(this.state.count)  // => 1
          })
          }}>{this.state.count}加一</button>
3. 
两次相同的setState 只会使用上一次操作的值来赋值到界面
<button onClick={() => {
          this.setState({count: this.state.count + 1})
          this.setState({count: this.state.count + 1})
  // => 界面只会输出1
        }}>{this.state.count}加一</button>
4. 
解决连续setState:可以将setState的第一个参数改为函数,获取上一次值,改变这个值就会连续生效了
或者将两个setState放在setTimeout里
<button onClick={() => {
          this.setState((preState, preProps) => {
            return {count: preState.count + 1}
          })
          this.setState((preState, preProps) => {
            return {count: preState.count + 1}
          })
  // => 界面输出2
        }}>{this.state.count}加一</button>

  // => 界面输出2
setTimeout(() => {
         this.setState({count: this.state.count + 1})
          this.setState({count: this.state.count + 1})
        });

二、事件的处理

  1. 箭头函数
  2. 参数类型可以将鼠标放在onClick上看到


    企业微信截图_1620294005701.png
这里必须是箭头函数,this才指向的是外部
  handleClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    e.target 是点击元素
   e.currentTarget  绑定事件的元素
   对target类型作判断
if ((e.target as HTMLElement).nodeName === 'SPAN') {
      this.setState({
        isOpen: !this.state.isOpen,
      });
    }
  };

  render() {
    return (
      <div>
        <button onClick={this.handleClick}>购物车</button>
      </div>
    );
  }
}
  1. 使用bind达到实现箭头函数效果
  constructor(props: Props) {
    // 调用React.Component基础类的构建函数
    super(props);
    this.state = {
      isOpen: false,
    };
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
    this.setState({
      isOpen: !this.state.isOpen,
    });
  };

三、hooks (钩子)

  1. 可以在非类组件中使用state,函数组件中需要处理副作用,用钩子把外部代码钩进来
  2. hooks 一律使用use作为前缀命名,常用钩子useState,useEffect,useContext(跨组件数据传递),useReducer(管理全局状态)useCallback(处理回调副作用),useRef(返回引用对象,不变)
  3. 有了hooks + 函数式组件, 不再需要类组件
    类组件冗长且复杂,难以复用。想要复用只能用 无状态组件 或 HOC (高阶组件)
1. 状态钩子 useState()
 这是react自带的hook函数,声明组件状态
 返回值是一个只有两个元素的数组:[状态,状态更新函数(函数命名规则:set + 状态名称)]
// 这种形式就不需要在jsx中用this.state.count 取值了
import React, { useState } from "react"; // 引入useState 
const App:React.FC = (props)=> {
  const [count, setCount] = useState<number>(0)
    return (
      <div >
        <button onClick={() => {
          setCount(count + 1)
        }}>{count}加一</button>
      </div>
    );
  
}


类组件中生命周期,state的改变,请求等操作对于函数(一个输入,只有一个返回值)来说都是副作用,
既然变为了函数式组件,就需要某种hooks来消除副作用

2. 副作用钩子 useEffect()
(1) 可以取代生命周期函数componentDidMount,componentDidUpdate和componentWillUnmount
(2)给函数式组件添加副作用(side effect)
  const [robotGallery, setRobotGallery] = useState<any>([])

  第二参数数组中写入变化的值,只有变化才触发第一参数的执行
  useEffect(() => {
    document.title = `${count}次`
  }, [count])
如果第二参数是空数组,就是在模拟类组件的componentDidMount,只会在第一次渲染执行
如果没有第二参数,就是在模拟类组件的componentDidUpdate,那在每次ui改变后都会执行该副作用函数(谨慎)
  useEffect(() => {
    fetch('https://jsonplaceholder.typicode.com/users')
    .then(res => res.json())
    .then(data => setRobotGallery(data))
// 使用async await
// const fetchData =  async ()  => {
//   const res = await fetch('https://jsonplaceholder.typicode.com/users')
 //  const data = await res.json()
//   setRobotGallery(data)
 //   }
//    fetchData()
  }, [])
  1. 使用上下文context (Provider,Consumer)跨组件传递 (vue有provide inject)
    或者用hooks useContext接收数据
父组件
// 使用上下文关系状态context跨组件传递数据
const defaultValue = {
  testName: 'jjj'
}
导出创造的上下文
export const nameContext = React.createContext(defaultValue)
ReactDOM.render(
  <React.StrictMode>
使用Provider 包裹子组件,并传递数据defaultValue
    <nameContext.Provider value={defaultValue} >
    <App />
    </nameContext.Provider>
  </React.StrictMode>,
  document.getElementById('root')
);


多级子组件 引入
1. 使用Consumer组件包裹组件内容。value就是传递来的数据
import {nameContext} from '../index'
const Robot: React.FC<RobotProps> = ({ id, name, email }) => {
  return (
    <nameContext.Consumer>
      {(value) => {
        return <li>
        <p>{value.testName}</p>
      </li>
      }}
    
    </nameContext.Consumer>
  );
};

2.   hooks :useContext  深度注入接受数据
引入useContext  
import React, {useContext} from "react";
const Robot: React.FC<RobotProps> = ({ id, name, email }) => {
  const value = useContext(nameContext)
  return (
    <li>
        <p>{value.testName}</p>
      </li>
  );
};

5 。全局context

  1. 新建appState.tsx. 导出AppStateProvider组件
// appState.tsx
import React, {useState} from 'react'

// 使用上下文context跨组件传递数据
interface AppStateValue {
   username: string,
   shoppingCart: {items: {id: number, name: string}[]}
}
const defaultValue: AppStateValue = {
   username: '123',
   shoppingCart: { items: [] }
 }
 
export const appContext = React.createContext(defaultValue)
//创建的context的初始值可以是undefined,
//类型定义React.Dispatch<React.SetStateAction<AppStateValue>> 
//在 <appSetStateContext.Provider > 组件上可以找到
export const appSetStateContext = React.createContext<React.Dispatch<React.SetStateAction<AppStateValue>> | undefined>(undefined)

创建context组件Provider用来传递数据
export const AppStateProvider: React.FC = (props) => {
   const [state, setState] = useState(defaultValue)

   Provider组件只能用value来传递数据,
   return <appContext.Provider value={state}>
// 需要修改shoppingCart,再创建一个context 用来传递setState方法。
// 两个Provider 嵌套
       <appSetStateContext.Provider value={setState}>
       {props.children}
       </appSetStateContext.Provider>
       </appContext.Provider>
}
  1. 在父组件引入AppStateProvider组件,包裹起子组件
import {AppStateProvider} from './AppState'

ReactDOM.render(
  <React.StrictMode>
    <AppStateProvider>
    <App />
    </AppStateProvider>
  </React.StrictMode>,
  document.getElementById('root')
);
  1. 在子组件中通过点击事件更新全局数据
import React, {useContext} from "react";
import {appContext, appSetStateContext} from '../AppState'

interface RobotProps {
  id: number;
  name: string;
  email: string;
}
// 组件传递数据使用的是props,接口定义传入的参数类型
const Robot: React.FC<RobotProps> = ({ id, name, email }) => {
useContext获取不同的context中的数据
  const value = useContext(appContext)
  const setState = useContext(appSetStateContext)
点击事件,执行传过来的setState 方法,实现改变了全局的shoppingCart数据
  const addToCart = () => {
    if (setState) {
      setState(state => {
        return {
          ...state,
           shoppingCart: {
             items: [
              ...state.shoppingCart.items,
               {id, name}
             ]
           }
        }
      })
    }
  }
  return (
    <li>
        <img alt="robot" src={`https://robohash.org/${id}`} />
        <h2>{name}</h2>
        <p>{email}</p>
        <p>作者:{value.username}</p>
        <button onClick={addToCart}>加入购物车</button>
      </li>
  );
};
上一篇 下一篇

猜你喜欢

热点阅读