React基础知识总结

React基础知识总结

引入React:
* CDN引入:要引入两个库,一个是react.development一个是react-dom.development
* umd引入
* webpack引入React基础知识总结_第1张图片
cjs和umd的区别:
* cjs全称CommonJS,是Node.js支持的模块规范
* umd是统一模块定义,兼容各种模块规范(含浏览器)
* 理论上优先使用umd,同时支持Node.js和浏览器
* 最新的模块规范是使用import和export关键字

使用react:
1. yarn global add create-react-app
2. create-react-app reactDemo
3. yarn start就会开启网页

普通代码和函数的区别:
* 普通代码立即求值,读取变量a的当前值
* 函数会等调用时再求值,即延迟求值。且求值时才会读取a的最新值
React基础知识总结_第2张图片
React基础知识总结_第3张图片

使用JSX:
React基础知识总结_第4张图片
React基础知识总结_第5张图片
React基础知识总结_第6张图片

babel-loader内置了jsx内部的功能
使用jsx的注意事项

  1. 要使用className
    n
    实际上是js代码,js中就是使用className,代码被转译成React.createElement('div',{className:'red'},'n')
  2. 插入变量
    标签里面所有JS代码都要用{}抱起来
    如果需要变量n,就用{}包起来
    如果需要对象,就要用{}把对象包起来,{{name:‘lulu’}}
  3. 习惯return后面加(),如果没有括号就相当于return undefined

Vue和React写HTML的方式:
* Vue写在.vue文件的里
* React把HTML混在JS、JSX的文件里

条件判断

const Component = () => {    
	return n%2===0?<div>n是偶数</div>:<span>n是奇数<span>
}

如果外面需要div,也可以写成:

const Component = () => {    
	return (
		<div>
			{n%2===0?<div>n是偶数</div>:<span>n是奇数<span>}        
		</div>   
	)
}

在React中什么都是js
还可以写成:

const Component = () => {
    const content = (
        <div>            
        	{n%2===0?<div>n是偶数</div>:<span>n是奇数<span>}        
        </div>    
    )
    return content
}

还可以写成:

const Component = () => {
	const inner = n%2===0?<div>n是偶数</div>:<span>n是奇数<span>
    const content = (
        <div>
            {inner}
        </div>   
    )    
    return content
}

还可以写成:

const Component = () => {
    let inner     
    if(n%2===0){        
    	inner = <div>n是偶数</div>    
    }else{
        <span>n是奇数<span>    
    }    
    const content = (
        <div>
            {inner}        
        </div>    
    )    
    return content
}

循环语句:

const Component = (props)  => {    
	return props.numbers.map((n,index)=>{
	    return <div>下标{index}的值为{n}</dev>
    })
}

外面需要div

const Component = (props)  => {    
	return (<div>
        {props.numbers.map((n,index)=>{
            return <div>下标{index}的值为{n}</div>
        })}    
        </div>)
}
const Component = (props) => {
      const array = []
      for (let i = 0; i < props.numbers.length; i++) {
        array.push(<div>下标{i}值为{props.numbers[i]}</div>)
      }
      return <div>{array}</div>
    }

React.createElement创建虚拟DOM对象,函数的作用是多次创建虚拟DOM对象
DOM Diff就是两次组件有不同,对它进行比较不同
使用{}插入JS代码




React组件:

Element(元素)VSComponent(组件):
如果一个函数返回了一个元素,那么它就叫做组件const Div = () => React.createElement('div'...)
下面是元素:const div = React.createElement('div'...)
React中有两种组件:
1. 函数组件

function Welcome(props) {
	return <h1>Hello,{props.name}</h1>
}

使用方法:
props.name是函数组件获取props数据的方式


2. 类组件

class Welcome extends React.Component {
    render() {
    return <h1>Hello,{this.props.name}</h1>
  }
}

使用方法:]
render()是用来渲染组件视图的
this.props.name是类组件获取props数据的方式

React.createElement的逻辑:

  • 如果传入一个字符串’div’,则会创建一个div
  • 如果传入一个函数,则会调用该函数,获取其返回值
  • 如果传入一个类,则在类前面加个new(这回导致执行constructor),获取一个组件对象,然后调用对象的render方法,获取其返回值

组件之间的通信(外部数据对的使用):
不能对外部数据props进行写的操作,只能读
1. 函数组件给类组件传递:
父组件:

function App() {
  const a = 0
  return (
    <div className="App">
      爸爸            //传递的值是字符串就用""包起来,是JS中的表达式就用{ }包起来
      <Son messasgeForSon={a}></Son>
    </div>
  )
}

子组件:

class Son extends React.Component {
  render() {
    return (
      <div className="Son">
        我是儿子爸爸对我说{this.props.messasgeForSon}
      </div>
    )
  }
}

2. 类组件给函数组件传递:
父组件:

class Son extends React.Component {
  render() {
    return (
      <div className="Son">
        <GrandSon messasgeForGrandSon="小子你好"></GrandSon>
      </div>
    )
  }
}

子组件:

const GrandSon = props => {
  return (
    <div className="GrandSon">
      我是孙子,我爸对我说{props.messasgeForGrandSon}
    </div>
  )
}

内部数据的使用:

类组件:

class Son extends React.Component {
  constructor() {
    super()
    this.state = { //初始化内部数据    
      n: 0
    }
  }
  add() {
    this.setState({ n: this.state.n + 1 })//写内部数据,产生的是一个新的n,并没有修改原来的n  
  }
  render() {
    return (
      <div className="Son">
        儿子n:{this.state.n} //读取内部数据
        <button onClick={() => this.add()}>+1</button>
      </div>
    )
  }
}
  1. this.state.n+=1无效
    其实n已经改变了,只不过UI不会自动更新而已
    调用setState才会触发UI更新
    因为React没有像Vue监听data一样监听state
  2. setState会异步更新UI
    setState之后,state不会马上改变,立马读state不是改变后的值
    所以要使用setState(函数)
  3. this.setState(this.state)不推荐
    React希望我们不要修改旧的state

类组件中如果setState只对其中的一部分进行改变,其他的部分会自动沿用之前的内容。
类组件的setState只会自动合并第一层的属性,并不会合并第二层的属性。
可以先把之前的复制过来再进行修改:

changeUser(){
  this.setState(
    user: {
    ...this.state.user,
    name = "jack"
    }
  )
}
add(){
  this.setState({ n: this.state.n + 1 })//这是一个异步更新UI的过程    
  console.log(n) //是+1之前的值
}
// 所以这样写不好,应该写成:
add(){
  this.setState(state => {
    const n = state.n + 1
    console.log(n)
    return { n }
  })
}

函数组件:

const GrandSon = () => {
  //初始值放在useState的括号里   
  const [n, setN] = React.useState(0)  //两个值,一个用来读,一个用来写 
  return (
    <div className="GrandSon">
      孙子n:{n}  //读
      <button onClick={() => setN(n + 1)}>+1</button>  //写,setN永远不会改变n,会产生一个新的n
    </div>
  )
}

如果把m,n放在一起,函数组件的setState不会自动合并,只要改了其中的一个,另一个值就会变成undefined,为了改变这种情况,应该先把之前的state进行拷贝:

<button onClick={() => setState(...state,n:state.n+1)}>+1</button>

事件绑定:

React基础知识总结_第7张图片
React基础知识总结_第8张图片

最终的写法:

addN = () => this.setState({n: this.state.n +1})
<button onClick={()=>this.addN()}></button>

类内部自己的函数放在原型上,箭头函数放在本身的对象上面

两种创建class的方式:

  1. ES5方式(由于不支持class关键字)
import React from 'react'
const A = React.createClass({
  render() {
    return (
      <div>hi</div>
    )
  }
})
export default A
  1. ES6的方式
import React from 'react'
class B extends React.Component {
  constructor(props) { //初始化     
    super(props) //会把props内容放到this上面    
  }
  render() {
    return (){
      <div>hi</div>
    }
  }
}
export default B
<B name="frank" onClick={this.onClick}>hi</B>

B组件接收到的props的值是{name:"frank", onClick:this.onClick, children: 'hi'}
如果内容不只是hi,children会被包装成数组
React基础知识总结_第9张图片

props的作用:

  1. 接收外部数据:
    只能读不能写
    外部数据由父组件传递
  2. 接受外部函数
    在恰当的时机,调用该函数
    该函数一般是父组件的函数

state(读)&setState(写)
初始化state:

class B extends React.Component {
  constructor() {
    super()
    this.state = { //初始化内部数据        
      user: { name: 'frank', age: 18 }
    }
  }
  render() { }
}

写的时候会shallow merge: setState会自动将新state与旧state进行一级合并

生命周期:
1. constructor() 在这里初始化state
2. static getDerivedStateFromProps()
3. shouldComponentUpdate() return false阻止更新
4. render() 创建虚拟DOM

5. getSnapshotBeforeUpdate()
6. componentDidMount() 组件已出现在页面
7. componentDidUpdate() 组件已更新
8. componentWillUnmount() 组件将死

9. static getDerivedStateFromError()
10. componentDidCatch
React基础知识总结_第10张图片

shouldComponentUpdate

用途:

  • 返回true表示不阻止UI更新
  • 返回false表示阻止UI更新

面试常问:
shouldComponentUpdate的作用:

  • 它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值

React会将旧的虚拟DOM和新的虚拟DOM进行对比,没有改变的话就不会更新UI
React基础知识总结_第11张图片

可以将React.Component替换成React.PurComponent,会在render之前比较新旧state的不同,但它只会对比一层,是浅对比
useState的实现:
首次渲染:把虚拟div变为真实div的过程
React基础知识总结_第12张图片1. setN

  • setN一定会修改数据x(不是n,他是一个中介位置),将n+1存入x
  • setN一定会触发重新渲染(re-render)

2.useState

  • useState肯定会从x读取n的最新值

3.x

  • 每个组件有自己的数据x,我们将其命名为state

尝试实现useState

// 让m和n不相互影响
let _state = []
let index = 0
const myUseState = initialValue => {
  // 只能将index的值保存下来,对currentIndex进行操作,不然无法让index加一 
  const currentIndex = index
  _state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]
  const setState = newValue => {
    _state[currentIndex] = newValue
    render()
  }
  index += 1
  return [_state[currentIndex], setState]
}
const render = () => {
  // 在渲染组件之前将index的值重置,否则会一直往上加 
  index = 0
  ReactDOM.render(< App />, rootElement)
}

但是上面这种方法出现两个问题:

  1. App用了_state和index,其他组件就没得用了
    解决方法:给每个组件创建一个_state和index

  2. 放在全局作=作用域中重名怎么办
    解决方法:放在组件对应虚拟节点身上

总结:

  • 每次重新渲染,组件函数就会执行
  • 对应的所有state都会出现分身
  • 如果不希望出现分身,就使用useRef/useContext等

React的Hooks:
1. 状态useState
2. 副作用useEffect
3. 上下文useContext
4. Redux useReducer
5. 记忆 useMemo
6. 引用 useRef
7. 自定义Hook

useState:

  1. 使用状态
    const [n,setN] = React.useState(0)
    const[user,setUser] = React.useState({name:‘F’})
  2. 注意事项1:不可局部更新
    如果state是一个对象,不能部分setState
    因为setState不会帮我们合并属性
    useReducer也不会合并属性
  3. 注意事项2:地址要变
    setState(obj)如果obj地址不变,那么React就认为数据没有变化

可以使用…user将原来的内容复制过来,再加上要修改的内容,相当于将前面的内容覆盖
React基础知识总结_第13张图片
可以写成const [user,setUser] = useState(()=>({name:"lala",age:9*9}))
JS中必须要在对象外面包括号,才知道里面是对象。要把它变成函数是因为JS引擎在遇到函数的时候并不会立即执行,否则的话JS引擎在每一次遇到它的时候都会计算一次9*9,浪费时间。
React基础知识总结_第14张图片

由于setState是异步执行代码的,所以要把setN中的内容替换成函数,点击的时候会将x或者是n自动替换成n
useReducer:

const initial = {
  n: 0
}
const reducer = (state, action) => {
  if (action.type === 'add') {
    return { n: state.n + action.number }
  } else if (action.type === "mult") {
    return { n: state.n * action.number }
  } else {
    throw new Error('unknown type')
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initial)
  const { n } = state
  const onClick = () => {
    dispatch({ type: 'add', number: 1 })
  }
  const onClick2 = () => {
    dispatch({ type: 'mult', number: 2 })
  }
  return (
    <div className="App">
      <h1>n:{n}</h1>
      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>*2</button>
    </div>
  )
}

代码四步走:
1. 创建初始值initialState
2. 创建所有操作reducer(state,action)
3. 传给useReducer,得到读和写API
4. 调用写({type:“操作类型”})

useEffect第二个参数是空[]时,表示只会渲染一次 。

useReducer代替Redux:步骤:
1. 将数据集中在一个store对象
2. 将所有操作集中在reducer
3. 创建一个Context
4. 创建对数据的读写API
5. 将第四步的内容放到第三步的Context
6. 用Context.Provider将Context提供给所有组件
7. 各个组件用useContext获取读写API

import React, { createContext, useReducer, useContext, useEffect } from 'react';
import ReactDOM from 'react-dom';
//1. 将数据几种在一个store对象
const store = {
  user: null,
  books: null,
  movies: null
}
//2.将所有操作集中在reducerconst
reducer = (state, action) => {
  switch (action.type) {
    case 'setUser':
      return { ...state, user: action.user }
    case 'setBooks':
      return { ...state, books: action.books }
    case 'setMovies':
      return { ...state, movies: action.movies }
    default:
      throw new Error()
  }
}
//3. 创建一个Contextconst
Context = createContext(null)function App() {
  //4. 创建对数据的读写API  
  const [state, dispatch] = useReducer(reducer, store)
  return (
    // 第一个花括号表示里面的是JS内容,里面的花括号代表是对象,只是使用ES6的语法缩写了  
    //5. 将第四步的内容放到第三步的Context 6. 用Context.Provider将Context提供给所有组件   
    <Context.Provider value={{ state, dispatch }}>
      <div>
        <User />
        <hr />
        <Books />
        <Movies />
      </div>
    </Context.Provider>)
}
function User() {
  //7. 各个组件用useContext获取读写API 
  const { state, dispatch } = useContext(Context)
  //useEffe第二个参数是空[]时,表示只会渲染一次 
  useEffect(() => {
    ajax("/user").then(user => {
      dispatch({ type: "setUser", user: user })
    })
  }, [])
  return (
    <div>
      <h1>个人信息</h1>
      <div>name:{state.user ? state.user.name : ""}</div>
    </div>
  )
}
function Books() {
  const { state, dispatch } = useContext(Context)
  useEffect(() => {
    ajax("/books").then(books => {
      dispatch({ type: "setBooks", books: books })
    })
  }, [])
  return (
    <div>
      <h1>我的书籍</h1>
      <ol>
        {state.books ? state.books.map(book => <li key={book.id}>{book.name}</li>) : "加载中"}
      </ol>
    </div>
  )
}
function Movies() {
  const { state, dispatch } = useContext(Context)
  useEffect(() => {
    ajax("/movies").then(movies => {
      dispatch({ type: "setMovies", movies: movies })
    })
  }, [])
  return (
    <div>
      <h1>我的电影</h1>
      <ol>
        {state.movies ? state.movies.map(movie => <li key={movie.id}>{movie.name}</li>) : "加载中"}
      </ol>
    </div>
  )
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
function ajax(path) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (path === "/user") {
        resolve({
          id: 1,
          name: "lu"
        })
      } else if (path === "/books") {
        resolve([{ id: 1, name: "JavaScript高级程序设计" },
        {
          id: 2,
          name: "JavaScript精粹"
        }
        ])
      } else if (path === "/movies") {
        resolve([
          {
            id: 1,
            name: "送你一朵小红花"
          },
          {
            id: 2,
            name: "美人鱼"
          }
        ])
      }
    }, 2000)
  })
}

useContext

  1. 上下文
  • 全局变量是全局的上下文
  • 上下文是局部的全局变量(Provider包着的地方可以访问)
  1. 使用方法
  • 使用C = createContext(intial)创建上下文
  • 使圈定作用域
  • 在作用域内使用useContext©来使用上下文
const Context = createContext(null)
function App(){
  const [n,setN] = useState(0)
  return (
    <Context.Provider value={{n,setN}}>
      <div className="App">
        <h1>hello</h1>
        <BaBa />
      </div>
    </Context.Provider>
  )
}


function BaBa(){
  return (
    <div>我是爸爸<Child /></div>
  )
}
function Child(){
  const {n,setN} = useContext(Context)
  const onClick = () => {
    setN(n=>n+1)
  }
  return (
  <div>我是儿子,我得到的n:{n}
    <button onClick={onClick}>+1</button>
  </div>
  )
}

注意事项:不是响应式的,更新的机制是重新渲染

useEffect(副作用):在浏览器渲染完成后执行

  • 对环境的改变即为副作用,如修改document.title
  • 但是不是一定要把副作用放在useEffect中
  • 实际上是每次render后执行

用途:
1. 作为componentDidMount使用,[]作第二个参数
2. 作为componentDidUpdate使用,可指定依赖
3. 作为componentWillUnmount使用,通过return
4. 以上三种用途可同时存在

特点:
如果同时存在多个useEffect,会按照出现次序执行

 useEffect(() => {
    console.log("第一次渲染后执行这一句话")
  }, []) //数组是空时就表示不会再次执行,只在第一次渲染时执行,作为componentDidMount使用
  useEffect(() => {
    if(n!==0){
      console.log("n变了")
    }
  },[n]) //[n]表示当n变化时再次执行代码,作为componentDidUpdate使用
  useEffect(() => {
    const id = setInterval(()=>{
      console.log("hi")
    },1000)
    return ()=>{
      window.clearInterval(id)
    }
  },[n])  //作为componentWillUnmount使用,通过return
  useEffect(() => {
    if(n!==0){
      console.log("变了")
    }
  })  //什么都不写时代表不论什么变化了都会执行

useLayoutEffect(布局副作用)在浏览器渲染完成前执行
特点:
useLayoutEffect总是比useEffect先执行
更优先使用useEffect(优先渲染),使用useLayoutEffect相当于截胡,会延迟屏幕的渲染时间,影响用户体验DOM======>useLayoutEffec=====>渲染页面======>useEffectuseMemo:memo也可以避免重复渲染,但是由于一旦增加了监听函数之后就不行了,所以出现了useMemo,它可以实现函数的重用

useMemo有点像缓存,希望两次新旧组件迭代的时候使用上一次的值就可以使用它

const onClickChild = useMemo(() => {
  return () => { }
}, [m]) //只有当m的值发生改变的时候返回函数的地址才会发生改变

特点:

  • 第一个参数是()=>value
  • 第二个参数是依赖[m,n]
  • 只有当依赖变化时,才会计算出新的value
  • 如果依赖不变,那么就重用之前的value

注意:

  • 如果你的value是个函数,那么要写成useMemo(()=>(x)=>console.log(x))
  • 这是一个返回函数的函数,由于不是很好看,所以就有了useCallback

使用useCallback改写上面的函数:

const onClickChild = useCallback(() => {
  console.log(m)
}, [m])

useRef:

目的:

  • 如果你需要一个值,在组件不断render时保持不变
  • 初始化:const count = useRef(0)
  • 读取:count.current
  • 需要current是为了保证两次useRef是同一个值(只有引用能做到)
  • 有点像函数外部的全局变量
  • 但是它不会自动刷新UI

自动刷新UI的方法:手动调用刷新

function App() {
  const count = React.useRef(0)
  const [_, set_] = React.useState(null)
  const onClick2 = () => {
    count.current += 1
    set_(Math.random)
  }
  return (
    <div className="App">
      <button onClick={onClick2}>update count:{count.current}</button>
    </div>
  )
}

小结:

  • useState/useReducer每次都会变化
  • useMemo/useCallback是有条件的变化,比如只有依赖[m]改变时才会变化
  • useRef是永远不变

forwardRef:

React基础知识总结_第15张图片

function App(){
  const buttonRef = useRef(null)
  return (
    <div className="App">
      <Button3 ref={buttonRef}>按钮</Button3 >
    </div>
  )
}
const Button2 = (props,ref) => {
  return <button className="red" ref={ref} {...props} />
}
const Button3 = React.forwardRef(Button2) //将外面的ref转发给Button2 的第二个参数

useImperativeHandle

  • 应该叫做setRef
  • 它的意图就是对ref进行设置

自定义Hook:

const useList = () => {
  const [list,setList] = useState(null)
  useEffect(() => {
    ajax("/list").then(list=>{
      setList(list)
    })},[])
    return {
      list,
      setList
    }
}

对上面Hook的使用:

function App(){
  // buttonRef相当于DOM对象的引用
  const {list} = useList()
  return (
    <div className="App">
      <h1>List</h1>
      {list?(<ol>{list.map(item=>(<li key={item.id}>{item.name}</li>))}</ol>):("加载中...")}
    </div>
  )
}

Stale Closure(过时闭包)
下面代码中的message和log就组成了一个过时的闭包
React基础知识总结_第16张图片

解决方法:
React基础知识总结_第17张图片

解法2:
React基础知识总结_第18张图片

解决办法:坚持使用函数作为setState的参数,这样就不会受制于变量是新的还是旧的,因为传递的是一个动作

React中解决这个问题的办法:就是加一个依赖

React基础知识总结_第19张图片

你可能感兴趣的:(React,react.js,javascript,前端)