React入门知识整理

188 阅读5分钟

一. 前置知识

  1. vue的模板语法有vue-loader
  2. react 的 jsx 语法使用的jsx-loader被babel-loader内置了,而babel-loader被webpack内置了
  3. JSX 注意事项
  • class属性 是className
    <div className ="red">n</div>

会被转译为React.createElement('div',{className:'red'},'n')

  • 变量和对象用花括号{}包起来

  • return 后面加括号()

  1. React 引入
yarn add react react-dom
import React from 'react'
import ReactDom from 'react-dom'

二. Class组件

1. 创建方式
    class Welcome extends React.Component{
        constructor(props){
           super(props) 
           this.state={
                n:0,
                m:1,
                user:{
                    name:"xiaohong",
                    age: 18
                }
            } 
        }
        render(){
            return (<h1>hello,{this.props.name}</h1>)
        }
    }
    //使用方法
    <Welcome name = "xiaoming">
2. 读取props
  • 不准写props,通知父组件修改
//传props,props会被包装成一个对象{name:"xiaohong",onClick:()=>{},children:"hi"}
class Parent extends React.component{
    constructor(props){
        super(props) 
        this.state ={name:"xiaohong"}
    }
    onClick =()=>{}
    render(){
        return <B name = {this.state.name} onClick={this.onClick}>hi</B>
    }
}
//读
 class B extends React.component{
    constructor(props){
        super(props) //这样写了以后,this.props就是外部数据  对象的地址  了
    }

    render(){
        return <div onClick = {this.props.onClick}>
        {this.props.name}
        <div>
            {this.props.children}
        </div>
        </div>
    }
}
3. 读写state

注意事项:

  • setState 会自动合并第一层属性,当有多层时使用Object.assign()或...操作符
  • this.state.n+=1 无效,必须调用 setState()才会触发UI更新
  • setState会异步更新UI,立马读state会失败,更推荐的方式是 setState(函数)
  • this.setState(this.state)不推荐,不要修改旧state(不可变数据,函数式编程理念)
  • this.setState((state,props)=>newState,fn) fn在写入成功后执行
//读
this.state.n 
//写
this.setState({n:1}) 
//多层属性时-方法1...操作符 ,m 和 n 会自动合并
this.setState({
    user:{
        ...this.state.user
        name:jack
    }
})
//多层属性时-方法2-Object.assign,m 和 n 会自动合并
const user =Object.assign({},this.state.user)
user.name ="xiaofang"
this.setState({
    user:user
})
//函数写法
this.setState((state)=>
    ( {n:state.n+1})
)
4. 事件绑定
//addN在对象上
class Son extends React.Component{
    constructor(props){
        super(props)
        this.state={
            n:0
        }
        this.addN=()=>{
            this.setState({n:this.state.n+1})
        }
    }
    render(){
        return <button onclick ={this.addN}>+1</button>
    }
}
//语法糖,把addN提到constructor外面,和上面的写法完全等价
class Son extends React.Component{
    constructor(props){
        super(props)
        this.state={
            n:0
        }
        
    }
    addN=()=>{
        this.setState({n:this.state.n+1})
    }
    render(){
        return <button onclick ={this.addN}>+1</button>
    }
}

//addN在原型上,如果不bind this ,this 会变成window,window上没有addN函数
class Son extends React.Component{
    constructor(props){
        super(props)
        this.state={
            n:0
        }
        
    }
    function addN(){
        this.setState({n:this.state.n+1})
    }
    render(){
        return <button onclick ={()=>this.addN()}>+1</button>
    }
}
//另一种
class Son extends React.Component{
    constructor(props){
        super(props)
        this.state={
            n:0
        }
        
    }
    function addN(){
        this.setState({n:this.state.n+1})
    }
    render(){
        return <button onclick ={this.addN.bind(this)}>+1</button>
    }
}
5. 生命周期钩子函数
1. constructor()  //初始化state
2. static getDerivedStateFromProps()
3. shouldComponentUpdate()  return false时阻止更新
4. render()  创建虚拟dom
5. getSnapshotBeforeUpdate()
6. componnetDidMount() //组件出现在页面
    获取DOM  
    Ajax请求 
    首次渲染执行此钩子
7. componentDidUpdate()  //组件已更新 
    也可以发起Ajax请求
    首次执行不会执行此钩子
    此处setState可能会触发无限循环,除非放在if里
    shouldCoponentUpdate返回false时不执行此钩子
8. componnetWillUnmount()  //组件将死
9. static getDerivedStateFromError()
10. componnetDidCatch()
//shouldComponentUpdat() return false时阻止更新
//React.PureComponent内置了此功能,当props和state未发生改变时就不会重新render
class App extends React.component{
    constructor(props){
        super(props)
        this.state = {
            n:1
        }
    }
    onClick = ()=>{
        this.setState(state=>({n: state.n + 1}))
        this.setState(state=>({n: state.n - 1}))
    }
    shouldComponentUpdate(newProps,newState){
        if(newState.n === this.state.n){
            return false
        }else{
            return true
        }
    }
    render(){
        console.log("render了一次")
        return(
            <div>
                {this.state.n}
                <button onClick = {this.onClick}>+1-1</button>
            </div>
        )
    }
}

钩子执行顺序: image.png

二. 函数组件

1. 创建方式
//第1种
function Welcome(props){
   return <h1>hello,{props.name}</h1>;
}
   
//第2种
const Welcome = props=> <div>{props.name}<div/>  
   
 //第3种
const Welcome = (props)=> {return <div>{props.name}<div/>}
   
//使用方法
<Welcome name = "xiaoming"/>
2. React Hooks

2.1. useState

  • 使用状态
const [n,setN] = useState(0)
const [user,setUser] = useState({name:"xiaohong"})
  • setState(obj)不会自动合并属性;
  • setState(obj) 如果obj地址没有变化,reac就认为地址没有变化
  • useState()接受函数,该函数返回初始state,且只执行一次
 const[state,setState] = useState(()=>{
     return initialState
 })
  • setState()接受函数,优先使用函数
    setN(i => i+1)
  • setN(n+1) 会产生一个新的n,原来的n并不会变

    例: 先加1再log,log出的是1; 先log再加1 ,log的是原来的n ,即0

    如果不希望出现分身,可以使用 useRef 或 useContext

function App(props){
    const [n,setN] = useState(0)
    const log =()=>{
        setTimeOut(()=>{
            console.log(`n:${n}`) 
        },3000)
    }
    return(
        <div className = "app">
           <p>{n}</p> 
           <p>
               <button onClick = {() => setN(n+1)}>+1</button>
               <button onClick = {log}>log</button>
           </p> 
        </div>
    )
}
  • useState简单原理
//自己实现--原理
import React from 'react'
import ReactDom from 'react-dom'
const rootElement = document.getElementById("root")

let _state = []
let index = 0
fucntion myUseState(initialValue){
    const currentIndex = index
    index+=1
    _state[currentIndex] = _state[currentIndex] || initialValue
    const setState= (newState)=>{
        _state[currentIndex] = newState;
        render()
    }
    return[_state[currentIndex],setState]
}

const render=()=>{
    index =0
    ReactDom.render(<App />,rootElement)
}

function APP(props){
    const [n,setN] = myUseState(0)
    const [m,setM] = myUseState(0)
    return(
        <div className = "app">
            <p>{n}</p>
            <p>
                <button onClick = {() => {setN(n+1)}}>+1</button>
            </p>
            <p>{m}</p>
            <p>
                <button onClick = {() => {setN(m+1)}}>+1</button>
            </p>
        <div>
    )
}

ReactDom.render(<App />,rootElement)

2.2 useReducer

  • 一. 创建初始值initialData
  • 二. 创建所有操作reducer(state,action)
  • 三. 传给useState,得到读和写API
  • 四. 调用写API({type:"xxx"})
import React, { useState, useReducer } from "react";
import ReactDOM from "react-dom";
const initial = {
    n: 0
};

const reducer = (state, action) => {
    if (action.type === "add") {
        return { n: state.n + action.number };
    } else if (action.type === "multi") {
        return { n: state.n * 2 };
    } 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: "add", number: 2 });
    };
    return (
    <div className="App">
        <h1>n: {n}</h1>
        <button onClick={onClick}>+1</button>
        <button onClick={onClick2}>+2</button>
    </div>
    );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

2.3. useContext

  • useContext不仅能贯穿始终,还能贯穿不同组件
  • useContext不是响应式样的,在一个模块将themeContext的值改变,另一个模块不会感知到这个变化
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const rootElement = document.getElementById("root");
const themeContext = React.createContext(null);
function App() {
    const [theme, setTheme] = React.useState("red");
    return (
        <themeContext.Provider value={{ theme, setTheme }}>
            <div className={`App ${theme}`}>
                <p>{theme}</p>
                <div>
                    <ChildA />
                </div>
                <div>
                    <ChildB />
                </div>
            </div>
        </themeContext.Provider>
    );
}
function ChildA() {
    const { setTheme } = React.useContext(themeContext);
    return (
        <div>
            <button onClick={() => setTheme("red")}>red</button>
        </div>
    );
}

function ChildB() {
    const { setTheme } = React.useContext(themeContext);
    return (
        <div>
            <button onClick={() => setTheme("blue")}>blue</button>
        </div>
    );
}
ReactDOM.render(<App />, rootElement);

2.4. useRef & forwardRef

  • 贯穿始终的状态
  • useRef 不仅可以用于div,还可以用于任意数据
  • 由于props种不包含ref,所以需要forwardRef
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
    const nRef = React.useRef(0);
    const update = React.useState()[1];
    const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
    return (
        <div className="App">
        <p>{nRef.current} 这里并不能实时更新</p>
        <p>
            <button onClick={() => {nRef.current += 1; update(nRef.current)}}>
                +1
            </button>
            <button onClick={log}>log</button>
        </p>
        </div>
    );
}
ReactDOM.render(<App />, rootElement);
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
    const buttonRef = useRef(null);
    return (
        <div className="App">
            <Button3 ref={buttonRef}>按钮</Button3>
        </div>
    );
}
const Button3 = React.forwardRef((props, ref) => {
    return <button className="red" ref={ref} {...props} />;
});
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

2.5. memo & useMemo & useCallback

  • react 默认有多余的render
  • 如果props不变,就没有必要再执行一次函数组件
  • 当props中有函数时,就需要使用useMemo来支持函数重用
  • useMemo第一个参数是 ()=>value,第二个参数是依赖[m,n]
  • 只有当依赖发生变化时,才会计算新的value,不变就重用之前的value
  • 如果value是个函数,就写成useMemo(()=>(x)=>{console.log(x)})
  • useCallback((x)=>{console.log(x)},[m])等价于useMemo(()=>(x)=>{console.log(x)},[m])
import React, { useMemo } from "react";
import ReactDOM from "react-dom";
import "./styles.css";

function App() {
    const [n, setN] = React.useState(0);
    const [m, setM] = React.useState(0);
    const onClick = () => {
        setN(n + 1);
    };
    const onClick2 = () => {
        setM(m + 1);
    };
    const onClickChild = useMemo(() => {
        const fn = div => {
            console.log("on click child, m: " + m);
            console.log(div);
        };
        return fn;
    }, [m]); // 这里呃 [m] 改成 [n] 就会打印出旧的 m
    return (
        <div className="App">
            <div>
                <button onClick={onClick}>update n {n}</button>
                <button onClick={onClick2}>update m {m}</button>
            </div>
            <Child2 data={m} onClick={onClickChild} />
        </div>
    );
}

function Child(props) {
    console.log("child 执行了");
    console.log("假设这里有大量代码");
    return <div onClick={e => props.onClick(e.target)}>child: {props.data}</div>;
}
const Child2 = React.memo(Child);

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

2.6. useEffect & useLayoutEffect

  • useEffect每次render后运行,(叫afterRender更好)
  • useEffect在浏览器渲染完成以后执行,useLayoutEffect在浏览器渲染前执行
  • useLayoutEffect总是比useEffect先执行

2.7. 自定义hook

//index.js
import React, { useRef, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import useList from "./hooks/useList";

function App() {
    const { list, deleteIndex } = useList();
    return (
        <div className="App">
            <h1>List</h1>
            {list ? (
                <ol>
                    {list.map((item, index) => (
                        <li key={item.id}>
                        {item.name}
                            <button
                                onClick={() => {
                                    deleteIndex(index);
                                }}
                            >
                                x
                            </button>
                        </li>
                    ))}
                </ol>
                ) : (
                "加载中..."
            )}
        </div>
    );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

//useList.js
import { useState, useEffect } from "react";

const useList = () => {
    const [list, setList] = useState(null);
    useEffect(() => {
        ajax("/list").then(list => {
            setList(list);
            });
        }, []); // [] 确保只在第一次运行
        return {
            list: list,
            addItem: name => {
            setList([...list, { id: Math.random(), name: name }]);
        },
        deleteIndex: index => {
            setList(list.slice(0, index).concat(list.slice(index + 1)));
        }
    };
};

export default useList;
function ajax() {
return new Promise((resolve, reject) => {
    setTimeout(() => {
            resolve([
            { id: "1", name: "Frank" },
            { id: "2", name: "Jack" },
            { id: "3", name: "Alice" },
            { id: "4", name: "Bob" }
            ]);
        }, 2000);
    });
}
5. 模拟声明周期
  • 函数组件执行的时候,相当于执行constructor
  • 函数组件的返回值就是render的返回值
  • React.memo 和 React.useMemo可以模拟shouldComponentUpdate
useEffect(()=>{console.log("第一次渲染")},[])  //第一次渲染
useEffect(()=>{console.log("任意属性变化")})  //任意属性变化
useEffect(()=>{console.log("n变了")},[n])  //n变了
useEffect(()=>{
    console.log("任意属性变化")
    return ()=>{
        console.log("组件要死了")
    }
})  //组件要死了

三. eventHub

image.png

// 数据
var money = {
  amount: 100000
}
var user = {
  id: 123123,
  nickname: '土豪'
}
var store = {
  money: money,
  user: user
}

// eventHub
var fnLists = {}
var eventHub = {
  trigger(eventName, data){
    let fnList = fnLists[eventName]
    if(!fnList){return}
    for(let i = 0; i<fnList.length; i++){
      fnList[i](data)
    }
  },
  on(eventName, fn){
    if(!fnLists[eventName]){
      fnLists[eventName] = []
    }
    fnLists[eventName].push(fn)
  }
}

var x = {
  init(){
    eventHub.on('我想花钱', function(data){  // subscribe
      store.money.amount -= data  // reducer
      render()
    })
  }
}
x.init()
class App extends React.Component {
  constructor(){
    super()
    this.state = {
      store: store
    }
  }
  
  render(){
    return (
      <div className="root"> 
        <BigPapa money={this.state.store.money} />
        <YoungPapa money={this.state.store.money}/>
      </div>
    )
  }
}

class BigPapa extends React.Component{
  constructor(){
    super()
  }
  render(){
    return (
    <div className="papa"> 大爸 {this.props.money.amount}
      <Son1 money={this.props.money}/>
      <Son2 money={this.props.money}/>
    </div>
  )
  }
}
class YoungPapa extends React.Component{
  constructor(){
    super()
  }
  render(){
    return (
    <div className="papa"> 二爸{this.props.money.amount}
      <Son3 money={this.props.money}/>
      <Son4 money={this.props.money}/>
    </div>
  )
  }
  
}
class Son1 extends React.Component{
  constructor(){
    super()
  }
  render(){
    return (
    <div className="son"> 儿子1 {this.props.money.amount}</div>
    )
  }
  
}
class Son2 extends React.Component{
  constructor(){
    super()
  }
  x(){
    // action
    eventHub.trigger('我想花钱' /*action type*/, 100) // payload
  }
  render(){
    return (
    <div className="son"> 儿子2 {this.props.money.amount}
      <button onClick={()=>this.x()}>消费</button>
    </div>
  )
  }
}

class Son3 extends React.Component{
  constructor(){
    super()
  }
  render(){
    return (
    <div className="son"> 儿子3 {this.props.money.amount}</div>
    )
  }
}
class Son4 extends React.Component{
  constructor(){
    super()
  }
  render(){
    return (
    <div className="son"> 儿子4 {this.props.money.amount}</div>
  )
  }
}

function render(){
  ReactDOM.render(<App/>, document.querySelector('#app'))
}

四. Redux

五. Dom Diff

六. CSS方案

七. Vue 和React对比

1. 编程模型对比

image.png