React 17 基础1

简介

  1. 设计理念
    单向数据流、虚拟 DOM、组件化
  2. 组件化编程的思想
    React 以组件的方式去重新思考用户界面的构成,将用户界面上每一个功能相对独立的模块定义成组件,然后将小组件通过组合或嵌套的方式构成大组件,最终完成整体 UI 的构建。

项目启动

npx create-react-app react-project --template typescript

CSS 模组

  1. 直接引入整个css文件

    import './index.css'
    使用:<div className="app" />
    
  2. JSS 模块化引入组件

    npm install typescript-plugin-css-modules --save-dev
    import styles from './index.module.css'
    使用:<div className={styles .app} />
    

state 和 props 的区别

  1. state 是组件对内的接口,props 是组件对外的接口
  2. state 用于组件内部的数据传递,props 用于数组间数据传递
  3. state:
    • state 是私有的,可以认为 state 是组件的“私有属性”,用 setState() 来修改。若直接修改 state,组件不会触发 render 函数,页面不会渲染。
    • 构建函数 constructor 是唯一可以初始化 state 的地方。调用 setState 后,state 不会立刻更新,是异步操作;不要依赖当前的 state 来计算下一个 state。
  4. props(Properties 的缩写):
    • 本质上说,props 是传入函数的参数,是传入组件内部的数据,是从父组件传递到子组件的数据。
    • props 是只读属性

React Event 事件处理机制

onClick
为了少输入代码,同时为了避免 this 造成的困扰,我们在这里使用箭头函数来进行事件处理。

handleClick = () => {
    this.setState({
      count: this.state.count + 1
    }, () => {
      // 数据同步
      console.log('count', this.state.count)
    })
    // 数据不同步
    console.log('click', this.state.count)
    // 说明 setState 是异步更新,同步执行的
}

render() {
    const { count } = this.state

    return (
      <div>
        <button onClick={this.handleClick}>{count}</button>
      </div>
    )
  }

生命周期

生命周期可分为三个阶段:


图片
  1. Mounting:初始化阶段,创建虚拟 DOM,渲染 UI
    constructor:初始化组件 state
    componentDidMount:在组件创建好 dom 元素以后,挂载进页面时调用,一般用来获取网络数据

  2. Updating:更新阶段,更新虚拟 DOM,重新渲染 UI
    componentWillReceiveProps:在组件接收到一个新的 prop(更新后)时被调用,但是会产生副作用,已被废弃,可使用 getDerivedStateFromProps 来进行替代。

    /*
     * getDerivedStateFromProps:将传入的props映射到state上面。这个函数会在每次 re-rendering 之前被调用,这意味着即使你的 props 没有任何变化,而是父 state 发生了变化,导致子组件发生了 re-render ,这个生命周期函数依然会被调用。
     * getDerivedStateFromProps 是一个静态函数,也就是这个函数不能通过 this 访问到 class 的属性,也并不推荐直接访问属性。而是应该通过参数提供的 nextProps 以及 prevState 来进行判断,根据新传入的 props 来映射到 state。
     * 如果 props 传入的内容不需要影响到你的 state,那么就需要返回一个 null,这个返回值是必须的。
     */
    static getDerivedStateFromProps(nextProps, prevState) {
      const {type} = nextProps;
      // 当传入的type发生变化的时候,更新state
      if (type !== prevState.type) {
        return { type }
      }
      // 否则,对于state不进行任何操作
      return null
    }
    

    shouldComponentUpdate:使用 shouldComponentUpdate() 以让 React 知道当前状态或属性的改变是否不影响组件的输出,默认返回 ture ,返回 false 时不会重写 render,该方法并不会在初始化渲染或当使用 forceUpdate() 时被调用

    shouldComponentUpdate(nextProps, nextState) {
      return true
    }
    

    componentDidUpdate:组件更新后调用

  3. Unmounting:删除虚拟 DOM,移除 UI
    componentWillUnmount:组件将要销毁时调用,可用来回收内存、移除监听或清空定时器等

获取网络 API 数据

对 any 的理解:

  1. 资源来源于网络请求,返回的数据类型不受控制
  2. 前端强行定义 API 数据类型,违法前后端分离的原则
  3. 不能为了使用 Type 而放弃 JavaScript 的灵活性
componentDidMount() {
  fetch('https://jsonplaceholder.typicode.com/users')
      .then(res => res.json())
      .then(data => {
        this.setState({
          robotGallery: data
        })
      })
}

React 17 新版本

  1. 虽然没有新功能,但具有战略意义
  2. 替换了底层的代码,向下兼容,同时甩掉了历史包袱
  3. 没有代码断层,给未来留下了足够的升级空间
  4. 逐步升级方案:允许多个 react 版本共存,如先升级路由系统、再升级弹窗,平滑过渡到最新版本
  5. breaking changes:
    事件委托机制改变
    向原生浏览器靠拢
    删除事件池
    useEffect 清理操作改为异步操作
    JSX 不可返回 undefined
    删除部分私有 API
  6. 展望:v17 不是过渡版本,而是承上启下的战略版本,未来将会更加强调函数事组件,支持微前端等

钩子 hooks 简介

  1. 消息处理的一种方法,用来监视指定程序
  2. 函数组件中需要处理副作用,可以用钩子把外部代码“钩”进来
  3. hooks 一律使用 use 前缀命名:useXxx
  4. 常用钩子:useState、useEffect、useContext、useReducer、useLayoutEffect、useDebugValue
  5. hooks 的本质:一类特殊的函数,为你的函数式组件(functional Component)注入特殊的功能
  6. hooks 的目的是为了给函数式组件加上状态

钩子 hooks 的使用

  1. useState:

    // useState 的参数为 count 的初始值,setCount 用于 count 的更新
    const [ count, setCount ] = useState<number>(0)
    const [ robotGallery, setRobotGallery ] = useState<any[]>([])
    
    <button onClick={() => setCount(count + 1)}>count:{count}</button>
    
  2. useEffect、loading、请求异常

    const [ loading, setLoading ] = useState<boolean>(false)
    const [ errMsg, setErrMsg ] = useState<string>('')
    /*
    * 一个组件内可以有多个useEffect
    * 若第二个参数不传,则在每次页面渲染UI后调用
    * 若第二个参数为空数组,则等同于 componentDidMount
    * 若第二个参数为非空数组,数组内参数变化后调用
    */
    useEffect(() => {
      console.log(count)
    }, [count])
    
    useEffect(() => {
      const getRobots = async () => {
        setLoading(true)
        setErrMsg('')
        try {
          const res = await fetch('https://jsonplaceholder.typicode.com/users')
          if (res.status === 200) {
            const data = await res.json()
            setRobotGallery(data)
          } else {
            setErrMsg(res.status + '')
          }
        } catch (error) {
          setErrMsg(error)
        }
        setLoading(false)
      }
    
      getRobots()
    }, [])
    
    { errMsg && errMsg.trim() && <h4>{errMsg.trim()}</h4>}
    {
       loading
       ?
       <h3>正在加载</h3>
       :
       <div className={styles['robot-layer']}>
         {
           robotGallery.map(item => <Robot key={item.id} id={item.id} name={item.name} phone={item.phone} email={item.email} />)
         }
       </div>
    }
    
  3. 父组件向子组件传递数据(3种)
    第一种:props

    // index.tsx
    <App usename="张三" />
    
    // App.tsx
    <h2>{props.usename}</h2>
    

    第二种:appContext.Consumer

    // index.tsx
    const defauleContextValue = { gender: '男' }
    export const appContext = React.createContext(defauleContextValue)
    
    // 孙子组件:Robot.tsx
    import { appContext } from '../index'
    
    const Robot: React.FC<RobotProps> = ({ id, name, phone, email }) => {
      return (
        <appContext.Consumer>
          {
             value => {
                return (
                  <div className={styles['robot-box']}>
                    <img src={`https://robohash.org/${id}`} alt="robot"/>
                    <p>name:{name}</p>
                    <p>phone:{phone}</p>
                    <p>email:{email}</p>
                    <p>gender:{value.gender}</p>
                  </div>
                )
             }
          }
        </appContext.Consumer>
      )
    }
    

    第三种:useContext

    // 孙子组件:Robot.tsx
    import { appContext } from '../index'
    
    const Robot: React.FC<RobotProps> = ({ id, name, phone, email }) => {
        const value = useContext(appContext)
        return (
            <div className={styles['robot-box']}>
                <img src={`https://robohash.org/${id}`} alt="robot"/>
                <p>name:{name}</p>
                <p>phone:{phone}</p>
                <p>email:{email}</p>
                <p>gender:{value.gender}</p>
            </div>
        )
    }
    
  4. 组件化 context provider
    创建 AppState.tsx 文件

    import React, { useState } from 'react';
    
    interface AppStateValue {
        gender: string
        ShoppingCart: {
            items: { id: number, name: string, count: number }[]
        }
    }
    
    const defauleContextValue: AppStateValue = {
        gender: '男',
        ShoppingCart: {
            items: []
        }
    }
    
    export const appContext = React.createContext(defauleContextValue)
    
    export const appSetStateContext = React.createContext<
        React.Dispatch<React.SetStateAction<AppStateValue>> | undefined
    >(undefined)
    
    export const AppStateProvider: React.FC = props => {
        const [ state, setState ] = useState(defauleContextValue)
    
        return (
            <appContext.Provider value={state}>
                <appSetStateContext.Provider value={setState}>
                    {props.children}
                </appSetStateContext.Provider>
            </appContext.Provider>
        )
    }
    

    index.tsx

    import { AppStateProvider } from './AppState'
    ReactDOM.render(
      <React.StrictMode>
        <AppStateProvider>
          <App />
        </AppStateProvider>
      </React.StrictMode>,
      document.getElementById('root')
    );
    

    子组件,变更全局状态

    import { appContext, appSetStateContext } from '../AppState'
    
    const value = useContext(appContext)
    const setState = useContext(appSetStateContext)
    
    const addToCart = () => {
        if (setState) {
            setState(state => {
                const items = JSON.parse(JSON.stringify(state.ShoppingCart.items))
                const itemIndex = items.findIndex(obj => obj.id === id)
                if (itemIndex !== -1) {
                    items[itemIndex].count++
                    return {
                        ...state,
                        ShoppingCart: {
                            items: [...items]
                        }
                    }
                }
                return {
                    ...state,
                    ShoppingCart: {
                        items: [...items, { id, name, count: 1 }]
                    }
                }
            })
        }
    }
    
    <button onClick={addToCart}>加入购物车</button>
    
  5. 高阶组件(HOC:高阶组件并不是组件,而是接受组件作为参数,并且返回组件的函数):withAddToCart
    AddToCart.tsx

    import React, { useContext } from 'react'
    import { appSetStateContext } from '../AppState'
    import { RobotProps } from './Robot'
    
    export const withAddToCart = (ChildComponent: React.ComponentType<RobotProps>) => {
        return props => {
            const setState = useContext(appSetStateContext)
            const addToCart = (id, name) => {
                  if (setState) {
                    setState(state => {
                        const items = JSON.parse(JSON.stringify(state.ShoppingCart.items))
                        const itemIndex = items.findIndex(obj => obj.id === id)
                        if (itemIndex !== -1) {
                            items[itemIndex].count++
                            return {
                                ...state,
                                ShoppingCart: {
                                    items: [...items]
                                }
                            }
                        }
                        return {
                            ...state,
                            ShoppingCart: {
                                items: [...items, { id, name, count: 1 }]
                            }
                        }
                    })
                }
            }
    
            return <ChildComponent {...props} addToCart={addToCart} />
        }
    }
    

    robot.tsx

    import React, { useContext } from 'react'
    import { appContext } from '../AppState'
    import { withAddToCart } from './AddToCart'
    
    export interface RobotProps {
        id: number
        name: string
        email: string
        phone: string
        addToCart: (id, name) => void
    }
    
    const Robot: React.FC<RobotProps> = ({ id, name, phone, email, addToCart }) => {
        const value = useContext(appContext)
    
        return (
            <div>
                <img src={`https://robohash.org/${id}`} alt="robot"/>
                <p>name:{name}</p>
                <p>phone:{phone}</p>
                <p>email:{email}</p>
                <p>gender:{value.gender}</p>
                <p><button onClick={() => addToCart(id, name)}>加入购物车</button></p>
            </div>
        )
    }
    
    export default withAddToCart(Robot)
    
  6. 自定义 hooks:useAddToCart

    export const useAddToCart = () => {
    const setState = useContext(appSetStateContext)
    
    const addToCart = (id, name) => {
        if (setState) {
            setState(state => {
                const items = JSON.parse(JSON.stringify(state.ShoppingCart.items))
                const itemIndex = items.findIndex(obj => obj.id === id)
                if (itemIndex !== -1) {
                    items[itemIndex].count++
                    return {
                        ...state,
                        ShoppingCart: {
                            items: [...items]
                        }
                    }
                }
                return {
                    ...state,
                    ShoppingCart: {
                        items: [...items, { id, name, count: 1 }]
                    }
                }
            })
          }
        }
    
        return addToCart
    }
    

    robot.tsx

    const addToCart = useAddToCart()
    

React 中,Component、Element、Instance 的区别?

  • 一个元素是一个普通对象,他描述一个组件实例和它所要求的的属性,或者一个 dom 节点和它所要求的属性
  • 组件封装了元素树,组件可以是类或者函数
  • 实例是 Component class 中 this 所指向的东西,即组件实例。Functional Component 没有实例
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容