react学习指南

入门

特点

  • 组件化,声明式编程
  • RN 移动端开发
  • 虚拟dom, diffing算法减少dom交互

虚拟dom创建的两种方式

jsx语法糖的最后会通过编译变成第二种写法

  • 利用jsx语法糖
  	 const jsx = <h1>这是一个jsx创建的虚拟dom</h1>;
	 let app = document.getElementById("app");
	 ReactDOM.render(jsx, app);
  • 利用createElement
	  const vNode = React.createElement("h1", { id: "title" }, "Hello,react");
      let app = document.getElementById("app");
      ReactDOM.render(vNode, app);

jsx语法规则

  • 关键字class必须用className代替
  • 混入js表达式必须用{}
  • style必须写在js表达式内
  • 单标签必须闭合
  • 有且只有一个根标签
  • 首字母大写表示组件,小写表示html标签
      const className = "test";
      const id = 7;
      const jsx = (
        <div>
          <h1 className="red" id={id}>
            关键字class需要用className代替
          </h1>
          <h1 style={{ fontSize: "13px", color: "#887" }}>
            样式必须写在对象内
          </h1>
          <input style={{ marginTop: "50px" }} type="text" />
        </div>
      );
      ReactDOM.render(jsx, document.getElementById("app"));
  • jsx 循环需要借助map; (对比v-for)
      const users = ["rose", "jock", "lily", "babe"];
      const jsx = (
        <ul>
          {users.map((item) => {
            return <li key={item}>{item}</li>;
          })}
        </ul>
      );
      ReactDOM.render(jsx, document.getElementById("app"));

组件

组件必须遵循大驼峰规则

函数式组件

函数式组件render过程,检测到组件时一个函数式组件,执行函数返回jsx,调用render方法

      function Demo() {
        return <h1>这是一个函数式组件名称为Demo</h1>;
      }
      ReactDOM.render(<Demo />, document.getElementById("app"));

类组件

类组件render过程,检测到组件是一个类组件,创建一个类实例,执行类实例上的render方法返回一个jsx,调用render方法
这就使得类组件中必须包含一个render方法且必须返回一个jsx对象

      class ClassDemo extends React.Component {
        render() {
          return <h1>这是一个名为ClassDemo的类组件,复杂场景下使用</h1>;
        }
      }
      ReactDOM.render(<ClassDemo />, document.getElementById("app"));

深入组件

state

  • state存放于组件实例身上,需要重写constructor添加
  • react中的事件不同于dom写法,需要符合小驼峰规则,且不需要()
  • 事件的回调函数的调用者是jsx而非组件实例,所以需要在constructor中借助bind改变this指向
  • 调用setState传递一个对象后,会合并对象,然后通知react重新执行render函数
      class ClassDemo extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            isHot: false,
          };
          this.changeWeather = this.changeWeather.bind(this);
        }
        render() {
          let { isHot } = this.state;
          return (
            <h1 onClick={this.changeWeather}>今天很{isHot ? "热" : "冷"}</h1>
          );
        }
        changeWeather(e) {
          this.setState({
            isHot: true,
          });
        }
      }
      ReactDOM.render(<ClassDemo />, document.getElementById("app"));

props

  • 每一个组件都有props
  • 为父组件添加属性后react会自动将其转换为props
  • 通过展开运算符…批量传入属性后,react内部会将其转换为属性传递的方式为组件添加props
  • 给类添加静态属性propTypes来限制props的类型
  • 给类添加静态属性 defaultProps 来设置props默认值
      class Student extends React.Component {
        static propTypes = {
          name: PropTypes.string.isRequired,
          age: PropTypes.number,
          sex: PropTypes.number,
        };
        static defaultProps = {
          sex: 1,
        };
        render() {
          let { name, age, sex } = this.props;
          return (
            <div style={{ marginTop: "30px" }}>
              <div>姓名:{name}</div>
              <div>年龄: {age}</div>
              <div>性别: {sex === 1 ? "男" : "女"}</div>
            </div>
          );
        }
      }

      var rose = {
        name: "rose",
        age: 99,
      };

      function StuList(props) {
        console.log(props);
        return (
          <ul>
            <li>{<Student name="jack" age={18} sex={0} />}</li>
            <li>{<Student {...rose} />}</li>
          </ul>
        );
      }
      ReactDOM.render(<StuList data="2" />, document.getElementById("app")); 

refs

创建ref总共有三种方式,以下将对每种方式进行优缺点对比

字符串回调函数createRef
操作简单,统一添加在refs中使用最多,写在函数内,添加到实例上未来趋势,结构规整
性能消耗大,官方不推荐可读性差,render时调用两次(可忽略)需要通过新增的[ref].current取
      class Student extends React.Component {
        sex = React.createRef();
        showRef = (e) => {
          console.log(this.refs.name, this.age, this.sex.current);
        };
        render() {
          let { name, age, sex } = this.props;
          return (
            <div style={{ marginTop: "30px" }} onClick={this.showRef}>
              <div ref="name">姓名:{name}</div>
              <div ref={(c) => (this.age = c)}>年龄: {age}</div>
              <div ref={this.sex}>性别: {sex === 1 ? "男" : "女"}</div>
            </div>
          );
        }
      }

      ReactDOM.render(
        <Student name="jack" age={18} sex={0} />,
        document.getElementById("app")
      );

forwardRef

ref转发

子组件通过forwardRef将内部的dom元素转发出去,从而达父组件操作子组件ref的目的

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

在hoc上的应用

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  // 注意 React.forwardRef 回调的第二个参数 “ref”。
  // 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
  // 然后它就可以被挂载到被 LogProps 包裹的子组件上。
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

事件

  • 自定义合成事件“onClick”作用在于处理兼容性
  • 事件是通过事件委托在组件最外层触发的,有益于减少性能消耗,e.target获取当前事件标签
  • react表单的双向绑定是通过监听onChange事件的修改实现的(非受控组件),通过表单提交(受控组件)
  • 在事件中自定义参数有两种方式,函数柯里化或者在回调中执行
      class Form extends React.Component {
        submit = (e) => {
          e.preventDefault();
        };
        change = (type) => {
          return (e) => {
            console.log(type, e);
          };
        };
        change2 = (type, e) => {
          console.log(type, e);
        };
        render() {
          return (
            <div>
              <form action="/" method="get" onSubmit={this.submit}>
                <input type="text" onChange={this.change("username")} />
                <input
                  type="text"
                  onChange={(e) => this.change2("password", e)}
                />
                <button type="submit">submit</button>
              </form>
            </div>
          );
        }
      }

生命周期

  1. 初始化阶段:ReactDOM.render触发 — 初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount()
  1. 更新阶段:组件内部setState或父组件重新render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
3. render()
4. getSnapshotBeforeUpdate()
5. componentDidUpdate()
  1. 卸载组件:ReactDOM.unmountComponentAtNode触发
1. componentWillUnmount()

路由

基本使用

  1. 有两种路由BrowserRouterHashRouter,所有路由的操作都必须在这两个组件内使用
  2. NavLinkLink都是用在跳转,区别在于NavLink会对当前选中的路由添加active类,添加replace属性将更换记录,to属性指定跳转路径
  3. Route用于指定路由页面,extra表示精准匹配,path表示匹配页面的路径,component用于指定组件
  4. Switch用做路由惰性匹配,保证只会匹配到第一个路由(默认命中多个展示多个)
  5. Redirect用在路由全部未命中的情况
  6. 当一个组件不是路由组件时,可以在导出时使用 withRouter 方法转为路由组件,这样才能使该组件可以正常操作路由
  7. 函数式跳转的方法在路由组件的props中可以看到
import React, { Component } from "react";
import {
  Route,
  NavLink,
  BrowserRouter,
  Switch,
  Redirect,
} from "react-router-dom";
import Message from "./Message";
import New from "./New";

export default class Home extends Component {
  render() {
    return (
      <div>
        <BrowserRouter>
          <div>
            <NavLink to="/home/message" style={{ paddingRight: "20px" }}>
              message
            </NavLink>
            <NavLink replace to="/home/new">
              new
            </NavLink>
          </div>
          <div>
            <Switch>
              <Route path="/home/message" component={Message}></Route>
              <Route exact path="/home/new" component={New}></Route>
              <Redirect to="/home/message" />
            </Switch>
          </div>
        </BrowserRouter>
      </div>
    );
  }
}

BrowserRouter与HashRouter

BrowserRouterHashRouter
原理H5的history API (历史记录)hash锚点
path优雅带#
刷新后state存在state消失
路径影响可能影响public文件夹下的某些相对文件路径

hook

useState

作用:使函数组件也具备state的状态,并进行读写操作
参数:初始化的值,并缓存到内部
返回:【状态值,更新状态函数】
更新状态的函数可以有两种写法,直接传值或者写一个返回值的函数

  let [count, setCount] = useState(0);
  return (
    <>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>方式1</button>
      <button onClick={() => setCount((state) => state + 1)}></button>
    </>
  );

useEffect

作用:可以执行副作用操作(生命周期)。副作用操作有ajax、设置订阅/定时器、操作dom等
参数:【副作用操作函数,需要监控的states】
传入的fn参数相当于componentDidMount+componentDidUpdate,他的返回fn相当于componentWillUnmount

  let [count, setCount] = useState(0);
  useEffect(() => {
    let timer = setInterval(() => {
      setCount(++count);
    }, 1000);
    return () => {
      clearInterval(timer);
    };
  }, []);
  return (
    <>
      <span>{count}</span>
    </>
  );

useRef

作用:获取ref对象,用法与createRef相似
场景:1. 获取ref引用,2.ref的转发不会因为render的变化而发生改变,所以可以用来记录上一次的值

  const [count, setCount] = useState(0);
  let countRef = useRef();
  function handleClick(params) {
    console.log(countRef);
  }
  return (
    <>
      <button ref={countRef} onClick={handleClick}>
        ref
      </button>
    </>
  );

useMemo

作用:缓存变量,可以用来自定义computed

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useCallback

作用:缓存函数,常伴随着React.memo一起使用,主要用于解决react中父组件render后,事件的引用发生变化从而导致的不必要性能消耗问题

const Count = () => {
  const [count, setCount] = useState(0);
  const [other, setOther] = useState(0);

  // 注意这里的变化 使用了useCallback
  const handleClick = useCallback(() => {
    setOther(other => other + 1);
  },[])

  return <Button onClick={handleClick}>{count}</Button>;
};

const Button = React.memo((props) => {
  console.log("刷新了");
  const { onClick, children } = props;
  return <button onClick={onClick}>{children}</button>;
});

// 只在初始化时打印了一次 刷新了
// 而后的每次点击 不在打印

触发render更新方法

  1. 外界props发生变化
  2. 组件内部调用this.setState
  3. 组件内部调用forceUpdate

组件通信方式

  1. 父子组件通过prop+prop回调实现通信
  2. ref获取组件实例来实现父子组件通信
  3. 兄弟组件通过将数组源放在同一个父组件上实现通信
  4. 祖孙组件通过createContext创建上下文容器,通过Provider+Consumer实现通信
  5. 兄弟组件通过将数组源放在同一个父组件上实现通信
  6. 以上几种都可以通过第三方emit库添加发布订阅实现通信
export const Context = createContext();
function GrandChild() {
  return (
    <div className="grand-child">
      {/* 函数组件读取祖先组件数据 */}
      <Context.Consumer>
        {(value) => {
          return <div>ParentId:{value.id}</div>;
        }}
      </Context.Consumer>
    </div>
  );
}

class Grand extends Component {
  // 类组件读取祖先组件数据
  static contextType = Context;
  state = {
    gid: "3",
  };
  render() {
    return (
      <div className="grand">
        <div>ParentId:{this.context.id}</div>
        <GrandChild />
      </div>
    );
  }
}
class Child extends Component {
  grand = createRef();
  componentDidMount() {
    //   回调的形式向父组件传参
    this.props.cid(2);
  }
  render() {
    return (
      <div className="child">
        <div>
          ParentId:{this.props.pid},GrandId:
          {this.grand.current?.state?.gid}
        </div>
        {/* 通过ref获取到子组件的值 */}
        <Grand ref={this.grand} />
      </div>
    );
  }
}

export default class Parent extends Component {
  state = {
    id: 1,
    cid: "",
  };
  getCid(cid) {
    this.setState({
      cid,
    });
  }
  render() {
    return (
      <div className="parent">
        {/* 爷爷组件通过 Provider+value向后代传递数据*/}
        <Context.Provider value={this.state}>
          <div>ChildId:{this.state.cid}</div>
          {/* 通过prop+回调实现父子组件通信 */}
          <Child pid={this.state.id} cid={this.getCid.bind(this)} />
        </Context.Provider>
      </div>
    );
  }
}

拓展补充

Fragment

概念:类似于template,不需要渲染标签使用
区别:都是类似template,不过Fragment可以加key,<>不可以

<Fragment><Fragment>
<></>

PureComponent

作用:与component不同的是,只有在组件state或props数据发生变化后才会触发render
注意:检测变化属于浅比较,因此不能通过引用修改值(与redux相似)
拓展:shouldComponentUpdate(preProp,preState)

class StaticComp extends PureComponent {
  render() {
    console.log("只有props和state变化才会打印");
    return <div>静态组件</div>;
  }
}
export default class Extra extends Component {
  state = {
    count: 0,
  };
  shouldComponentUpdate(preP, preS) {
    return preS.count < 5;
  }
  render() {
    let { count } = this.state;
    return (
      <div>
        <span onClick={() => this.setState({ count: count + 1 })}>{count}</span>
        <StaticComp />
      </div>
    );
  }
}

插槽

  • 默认插槽使用props.children获取,但是无法获取到插槽外父组件的值
  • render props 通过props函数回调的方式获取到父组件传递过来的参数
function Parent(props) {
  let [parentName, setParentName] = useState("parent--name");
  return (
    <div>
      {/* 默认插槽渲染 */}
      <div>default-slot:{props.children}</div>
      {/* 具名自定义插槽渲染 */}
      <div>render-slot:{props.render(parentName)}</div>
    </div>
  );
}
function Child(props) {
  return <span>child:{props.data}</span>;
}
export default class Extra extends Component {
  render() {
    return (
      <div>
        {/* 通过指定prop传递子组件并获取到父组件传递过来的值(作用域插槽) */}
        <Parent render={(d) => <Child data={d} />}>
          <span style={{ color: "red" }}>9</span>
        </Parent>
      </div>
    );
  }
}

HOC高阶组件(mixins)

  • 定义:传入一个组件,对组件进行加工并返回
  • 本质:一个函数,传入一个组件,对组件进行加工(将mixinData通过prop进行传递),返回加工后的组件
  • 原则:
    1. 只是用作加工
    1. 不能改变原组件
    1. 相当于一个组件的装饰器
    1. 高阶组件名称以with开头,显示名称以With开头
    1. 禁止在render中使用高阶组件
    1. 如果存在静态方法务必手动复制
// hoc高阶组件
const withHoc = (WarpedComponent) => {
  return class extends Component {
    state = {
      data: null,
    };
    componentDidMount() {
      let data = localStorage.getItem("token") || "test";
      this.setState({
        data,
      });
    }
    render() {
      return <WarpedComponent data={this.state.data} {...this.props} />;
    }
  };
};

class About extends Component {
  render() {
    return <div>{this.props.data}</div>;
  }
}

export default withHoc(About);

错误边界

  • 静态方法getDerivedStateFromError的返回值将会被合并到state中
  • componentDidCatch钩子会捕获到错误,一般用于发送请求通知后端
class Child extends Component {
  render() {
    return (
      <ul>
        {this.props.list?.map((item) => {
          return <li key={item}>{item}</li>;
        })}
      </ul>
    );
  }
}
export default class Extra extends Component {
  state = {
    list: "",
    // list: ["jack", "rose"],
  };
  //   返回值将合并到state中
  static getDerivedStateFromError(e) {
    return {
      hasError: true,
    };
  }
  // 统计页面的错误。发送请求发送到后台去
  componentDidCatch(error, info) {
    console.log(error, info);
  }
  render() {
    let { hasError } = this.state;
    return (
      <div>{hasError ? "error" : <Child list={this.state.list}></Child>}</div>
    );
  }
}

redux

核心组成

action

  • 概念:将数据从应用传到store的载体,也是store数据的唯一来源,通过store.dispatch(action)action传递给store
  • 本质:一个js普通对象,内部必须有一个type属性来表明需要执行的动作
  • 形式:对象形式创建函数形式(一个函数返回值action普通对象
  • 注意点:action只是描述了有事情会发生,起到一个通知与载体(传递数据)作用

reducer

  • 概念:store的一个处理工厂,检测到stroe.dispath后会自动执行,对原有的state进行加工(结合传递过来的action对象)最终达到更新store的目的
  • 本质:一个纯函数,根据action旧的state计算出新的state并返回(返回值是新的state
  • 形式:纯函数,arg1:旧的state,arg2:action对象返回值:新的state

store

概念:维持应用的state,建立起action和reducer的连接
API:

  1. 通过createStore(reducer)创建store对象
  2. 提供getState()方法获取对象
  3. 提供dispatch(action)方法发送action
  4. 通过subscribe()方法注册监听
  5. 通过subscribe()返回值来注销监听

react-redux

作用:react中更便捷的读取redux中的store数据,向store中分发action来更新数据

Provider

作用:整个app的store供应者,包裹在根组件的最外层,使得所有子组件都可以拿到state
使用:接收store作为props,通过context往下传递,这样react中任何组件旧都可以通过context获取到store

connect

作用:通过该方法可以将组件store进行关联
写法类似hoc高阶组件
使用:connect(option)(component)
option参数:mapStateToProps,mapDispatchToProps

  • 参数一,【mapStateToProps】是个函数,参数1是store中的state,函数必须有一个对象返回值,返回的对象属性将会被映射到props中
  • 参数二,【mapDispatchToProps】【fn , obj , undefined
  1. 如果为则会在props中添加一个dispatch属性
  2. 如果是fn回调,那回调的第一个参数就是dispatch,需要返回一个包含方法的对象,在方法中调用dispatch,函数放回的方法将会被映射到props中
  3. 如果是对象,直接映射到props中

代码展示

// 容器代码
class Extra extends Component {
  render() {
    console.log(this.props);
    return (
      <Provider store={store}>
        <div onClick={() => this.props.dispatch(actions.add(3))}>
          {this.props.reducer.count}
        </div>
      </Provider>
    );
  }
}
export default connect((state) => {
  return state;
})(Extra);


---------------------------------------
// redux代码

// 初始化store
import { createStore, applyMiddleware, combineReducers } from "redux";
//引入redux-thunk,用于支持异步action
import thunk from "redux-thunk"; // 引入dev-tools
import { composeWithDevTools } from "redux-devtools-extension";


export const actions = {
  add: (num) => ({ type: "add", num }),
  // 异步action返回一个函数
  asyncAdd: (num) => {
    return (dispatch) => {
      setTimeout(() => {
        dispatch({ type: "add", num });
      }, 1000);
    };
  },
};

const initState = {
  count: 0,
};

const reducer = (state = initState, action) => {
  let { type } = action;

  switch (type) {
    //   state数据不能使用引用修改
    case "add":
      state = {
        count: state.count + action.num,
      };
      return state;
    default:
      return state;
  }
};

export default createStore(
  // 合并多个reducer
  combineReducers({ reducer }),
  //   添加到devtool中 => 使用异步插件
  composeWithDevTools(applyMiddleware(thunk))
);


对比vuex

核心概念

reduxvuex
action(同步action,或借助中间件实现异步action,action不改变store,仅用于描述store的变更type)mutation(同步操作)、action(异步操作提交mutation)
reducer(纯函数),根据action和旧的store计算出新的storemutation直接修改state
state(单一数据源)state(单一数据源)
combineReducers组合reducermodule

补充

  • redux通过store.getState()获取store树,通过store.subscribe(listener)订阅store的变化,当store变化时会触发subscribe订阅的listener监听器,在监听器中通知react重新render
  • redux可以通过react-redux将状态映射到组件,通过mapStateToProps获取store的状态,vuex则是有mapStatemapActionmapMutations 等API
  • redux通过combineReducers结合多个reducer,vuex通过module实现嵌套子关系

使用原则

  • redux
  1. 单一数据源(一个redux应用只有一个store)
  2. state只读(唯一改变state的方法就是通过触发action,action时一个用于描述以发生事件的普通对象
  3. 使用纯函数(reducer)来修改state
  • vuex
  1. 状态集中到state中管理
  2. mutation是更改状态的唯一方式
  3. action中进行异步逻辑并commit mutation

使用图解

redux
在这里插入图片描述
vuex
在这里插入图片描述

踩坑记录

  1. reducer中修改state的值不要通过引用修改,react的坑(引用值地址一致,无法触发render更新)
  2. connectmapDispath默认可以不传
  3. 编码上性能细节,jsx模版中的表达式(函数字面量|对象字面量)应当使用变量接受,这样会避免一些不必要的性能消耗。
  4. state的更新是异步的,可以通过useEffect使用最新值

性能优化记录

运行层

出发点:1. 减少render数量;2. 减少计算次数
具体:合理的拆分组件

  1. 类组件通过shouldComponentUpdatePureComponent减少静态组件的render次数
  2. 函数组件通过memo
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值