react学习

396 阅读11分钟

一、react类组件

setState

  • setState(updater, [callback])

  • updater: 更新数据 FUNCTION/OBJECT

  • callback: 更新成功后的回调 FUNCTION

  • 异步: react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能

  • 浅合并 Objecr.assign(),通过setState 修改状态时,只需要传入要修改的state即可,setState 会帮助我们进行浅合并,将 修改sttae 合并入 组件的state 中。

  • 调用 setState 之后,会触发生命周期,重新渲染组件

注意事项

  • 如果 state 中的数据,是一个引用类型,要修改它,一定要记得返回一个新的引用

    addTodo=(newTodo)=>{ const {data} = this.state; // 在 React 不要直接修改状态 /* data 是一个引用类型,这里直接修改 data 相对于修改 this.state.data

        在 React 直接修改 this.state,在更新之后,就没有办法获取到更新之前的状态
    */
    

    let newData = [...data,{ id: Date.now(), todo: newTodo, done: false }]; // data.push({ // id: Date.now(), // todo: newTodo, // done: false // }); // 调用 setState 完成对 data 的修改 this.setState({ data:newData }); }

组件间通信

  • 在 React.js 中,数据是从上自下流动(传递)的,也就是一个父组件可以把它的 state / props 通过 props 传递给它的子组件,但是子组件不能修改 props - React.js 是单向数据流,如果子组件需要修改父组件状态(数据),是通过回调函数方式来完成的。

父级向子级通信

  • 把数据添加子组件的属性中,然后子组件中从props属性中,获取父级传递过来的数据

子级向父级通信

  • 在父级中定义相关的数据操作方法(或其他回调), 把该方法传递给子级,在子级中调用该方法父级传递消息

跨组件通信

  • |

    // App.js中引入child.js,将 value 值传到 subschild.js 孙子组件中 return <> <Provider value={{ count, name, setCount:this.setCount }} >
    </>

    // 子组件child.js return

    数据修改

    {/* 旧做法,一层层往下传 /} {/ /} {/ <SubChild {...this.props} /> */}

    import { Component } from "react"; import context, { Consumer } from "./context"; class SubChild extends Component { render() { // const {count,name} = this.props; // console.log(this.props); return {(context) => { const { count, name } = context; return

    count:{count}

    name:{name}

    }} } }

    export default SubChild;

  • | context

    import { Component } from "react"; import context, { Consumer } from "./context"; class SubChild extends Component { static contextType = context;// contextType ,告诉当前组件,当前组件中 context 值,从哪个 context 中获取。 render() { const { count, name, setCount } = this.context; return

    count:{ count}

    name:{name }

    <button onClick={()=>{ setCount(count +1); }}>递增
    } }

    export default SubChild;

受控组件与非受控组件

  • 受控组件: 想要获取表单的一些内部状态时,就可以将表单的内部状态和组件的状态进行绑定,这样就形成受控组件

    • checked:将 组件的状态,赋值给控件的 checked 属性,通过 onChange 事件监听控件的 checked 属性发生变化,再赋值给 组件的 state

    • value: 将 组件的状态,赋值给控件的 value 属性,通过 onChange 事件监听控件的 value 属性发生变化,再赋值给 组件的 state

  • 非受控组件:

    • 需要设置初始值,只设置初始值,并不希望该控件受控,不要用value|checked(只要用 value|checked)react 就认为我们做受控组件. 这里要用 defaultValue 和 defaultChecked

    • 不需要设置初始值

生命周期函数

mount 挂载阶段 --- 组件从初始化到真实渲染到DOM中

  • constructor 组件初始化

  • static getDerivedStateFromProps(props)

  • 注意 this 问题

  • render

  • componentDidMount -- 处理副作用(请求)

update 更新阶段

(从setState之后组件开始更新,一直完成对真实的DOM节点更新)

  • static getDerivedStateFromProps(props, state)

  • shouldComponentUpdate() -- 判断是否更新

  • render()

  • getSnapshotBeforeUpdate()

  • componentDidUpdate() -- 处理副作用(请求)

unmount 卸载阶段

  • componentWillUnmount 组件即将卸载

副作用:

  • 获取真实 DOM 节点

  • 数据请求

常用的三个生命周期函数:

shouleComponentUpdate()

用于判断组件更新与否,优化性能

shouldComponentUpdate(nextProps,nextState){
      //nextProps 更新后的props, nextState 更新后的state
      //console.log(nextState,this.state);
      console.log(2,"判断组件是否需要更新");
      return true; //返回值为 true 则继续执行更新,返回值为false 则不执行更新,后续的生命周期函数,也不会执行
  }

componentDidUpdate()

组件更完成更新,比对更新前后数据差异做相应的逻辑处理

componentDidUpdate(prevProps,prevState,prevDOM){

  // console.log(prevDOM);

  console.log(5,"组件更新完成");

 }

componentDidMount()

componentDidMount(){
    console.log(3,"组件挂载完成,虚拟DOM,已经生成了真实DOM");
  }

children API

App.js

class App extends Component {
  render() {
    // 调用组件时,如果在标签对中间写内容,该内容会传递给子组件的props.children 属性
    return <Child>
        <div>明天周末了,要约一块做练习吗</div>
        <button>约</button>
        <button>不约,练习太少已经做完了</button>
        {()=>{
          alert("你想干啥")
        }}
    </Child>
  }
}

child.js

通过 props将父组件中的元素传进

class Child extends Component {
  render(){
    //console.log(this.props);
    this.props.children[3]();
    return <div>
        <h1>children</h1>
        {this.props.children.slice(0,3)}
    </div>
  }
}

dangerouslySetInnerHTM API

用于解析含html标签的数据

import { Component } from "react";
const data = `<h2>文章标题</h2><p>段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1</p><p>段落2段落2段落2段落2段落2段落2段落2段落2段落2</p>`;
// class App extends Component {
//   render() {
//     return <div ref={(node)=>{
//         node.innerHTML = data;
//     }}>
//     </div>
//   }
// }
class App extends Component {
  render() {
    return <div
      dangerouslySetInnerHTML={{
        __html:data
      }}
    >
    </div>
  }
}

二、react函数式组件

介绍:

  • 函数是组件,本质就是一个函数,接收参数 props 并返回 reactElement

  • 函数式组件中没有 this 和生命周期函数,不能使用 string ref

  • 使用函数式组件时,应该尽量减少在函数中声明子函数,否则,组件每次更新都会重新创建该子函数

ReactHooks:

常用 hook

useState

const [state, setState] = useState(initialState);
const [状态,修改该状态的方法] = useState(初始值);

hooks 使用注意事项:
    1. hook 必须在组件的最外层调用,不能包含在 if for 或 子函数 等等

    内置 hook
      useState 定义状态 
        `const [state, setState] = useState(initialState);`
            let [状态,修改该状态的方法] = useState(初始值);
            1. 在同一个组件中可以使用 useState 定义多个状态
            2. 注意 useState 返回的 setState 方法,不会进行对象合并
            3. 注意 useState 返回的 setState 方法同样是异步方法

/*
  函数式组件在更新时,是整个函数重新执行一遍
*/
/*
mount阶段:
 rows1: const [count,setCount] = useState(0)
    let state = [0];
    return [state[0],(nub)=>{state[0] = nub; updater()}]
 rows2:const [name,setName] = useState("");
    state.push(""); ---> state=[0,""]
    return  [state[1],(data)=>{state[1] = data; updater()}]

updater阶段:
   rows1: const [count,setCount] = useState(0);
   return [state[0],(nub)=>{state[0] = nub; updater()}]

*/

挂载阶段和更新阶段原理图类似

useEffect

useEffect:副作用 hook,用于替代生命周期
   useEffect(function{ ---> effect 函数
      return ()=>{ ---> 返还函数

      }
   },[依赖参数])
*/
/*
组件挂载阶段:
   1. 执行 useEffect 将 effect 函数存入队列
   2. 挂载完成之后,执行 effect 函数队列,并获取返回函数存入队列
组件更新阶段:
   1. 执行 useEffect 将 effect 函数存入队列
   2. 更新完成之后,先将返回函数队列执行: 在执行时会观察该 effect 是否有依赖参数,无依赖数据,直接执行,有依赖则追踪依赖是否改变,改变才执行,不变则不执行
   3. 执行新的  effect 函数存入队列: 在执行时会观察该 effect 是否有依赖参数,无依赖数据,直接执行,有依赖则追踪依赖是否改变,改变才执行,不变则不执行
卸载阶段:
   1. 将返回函数队列执行

useEffect()执行过程以及参数设置:

// 没有依赖数据,挂载、更新、卸载阶段都执行
  useEffect(()=>{
    console.log("effect-1");
    return ()=>{
      console.log("effect 返还函数 - 1");
    }
  })
  // 挂载和卸载阶段执行
  useEffect(()=>{
    console.log("effect-2");
    return ()=>{
      console.log("effect 返还函数 - 2");
    }
  },[])
  // 有依赖数据,数据更新时候,更新执行,其他俩个都执行
  useEffect(()=>{
    console.log("effect-3");
    return ()=>{
      console.log("effect 返还函数 - 3");
    }
  },[count])

useEffect()用于替代生命周期函数版本一:

/*
useEffect 作用:副作用函数用来替代生命周期
  componentDidMount、componentDidUpdate 和 componentWillUnmount

*/
// 挂载时 和 更新时 都执行
function Child(){
  const [count,setCount] = useState(0);
  const [name,setName] = useState("a");
  // 组件挂载时和count有变化时执行
  useEffect(()=>{
    console.log("请求数据","组件挂载时和count有变化时执行")
  },[count]);
  // 只在组件挂载阶段执行
  useEffect(()=>{
    console.log("只在挂载阶段执行");
    return ()=>{
      console.log("组件的即将卸载");
    }
  },[])

useEffect()用于替代生命周期函数版本二:

/*
useEffect 作用:副作用函数用来替代生命周期
  componentDidMount、componentDidUpdate 和 componentWillUnmount

*/
// 挂载时 和 更新时 都执行
function Child(){
  const [count,setCount] = useState(0);
  const [name,setName] = useState("a");
  const isMount = useRef(true);
  // 组件挂载时和count有变化时执行
  useEffect(()=>{
    console.log("请求数据","组件挂载时和count有变化时执行")
  },[count]);
  // 只在组件挂载阶段执行 componentDidMount()
  useEffect(()=>{
    console.log("只在挂载阶段执行");
    // componentWillUnmount()
    return ()=>{ // 只在组件卸载的时候
      console.log("组件的即将卸载");
    }
  },[]);
  // 当组件更新时 ComponentDidUpdate()
  useEffect(()=>{
   if(!isMount.current){
      console.log("组件更新")
   } else {
      isMount.current = false;
   }   
  })

useRef

  • useEffect 类组件 componentDidMount、componentDidUpdate 和 componentWillUnmount 需要清除的副作用

  • useRef 用户关联原生DOM节点,或者用来记录组件更新前的一些数据

    const prevCount = useRef(count); // 当 Ref 中保存的是数据时,数据并不会随着组件的更新自动更新 useEffect(()=>{ console.log(prevCount.current); prevCount.current = count; })

React Hooks 优势

  • 简化组件逻辑

  • 复用状态逻辑

  • 无需使用类组件编写

Hook 使用规则

  • 只在 React 函数中调用 Hook

    • React 函数组件中

    • React 自定义 Hook 中

  • 只在最顶层使用 Hook

自定义hook

import { useEffect, useState } from "react";

function useScroll() {
    const [Y,setY] = useState(0);
    useEffect(()=>{
      // 初始Y
      setY(window.scrollY);
      window.onscroll = ()=>{
          setY(window.scrollY);
      };
      return ()=>{
        window.onscroll = null;
      }
    },[]); 
    return [Y,(Y)=>{
      window.scrollTo(0,Y);
      // 更新Y值
      setY(Y);
    }];
}

export {useScroll};

三、react-route

组件

BrowserRouter 组件 -- history

基于 HTML5 History API 的路由组件

HashRouter 组件 -- hash

基于 URL Hash 的路由组件

/*
  设置路由模式:history(BrowserRouter) 、hash(HashRouter)
*/
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter> ,
  document.getElementById('root')
);

Route 组件

Route使用方式(Switch | Redirect)

Route 知识点
  • path:该路由要匹配的路径

  • 匹配模式:

    • 默认:默认情况 path 是 模糊匹配:url 以 path 为开始时 则匹配成功

    • exact: 精确匹配: url 为 path 或 path/ 时匹配

    • strict 严格匹配: url === path 可以匹配,设置严格匹配时必须先设置 exact

  • 多匹配路径

  • component: 路径匹配成功之后,要显示的视图

  • render:和component起着类似的作用,但可以做一些视图显示前的判断处理

    <Route path="/about"
    	exact
    	render={(routerProps)=>{
    		//console.log(routerProps);
             //render不会将参数自动传递,需要手动传入
    		return <AboutPage {...routerProps} user={user}  /> 
        }}
    />
    
Link | NavLink

NavLink:

  • NavLink 作用跟 Link 类似,都是提供了一个视图跳转链接

  • NavLink 可以给当前选中项,添加选中效果

  • NavLink 在匹配当前项时,默认也是 模糊匹配

    • activeClassName:当前选中项的class,默认为 active

    • activeStyle

    • isActive,isActive 接收一个回调函数,通过该回调函数的返回值,决定项当前要不要选中,返回值 为 true 则 选中当前项,false 不选中

Switch

常用来匹配其他非法路径(404页面的应用)

Redirect

重定向

App.js

function App() {
  return <div className="wrap">
      <Nav />
      <hr />
      <Switch>
          <Route path={["/","/home","/index"]} exact strict component={IndexPage} />
          <Route path="/about" exact  component={AboutPage} />
          <Route path="/about/details" component={AboutDetailsPage} />
          {/*
              <Route path="/404" component={Page404} />
              <Redirect to="/404" />
          */}
    	  <Route path="/list/:t?/:p?" exact 
            render={({match})=>{
              const {t="good",p="1"} = match.params;
              if(types.includes(t)
              && parseInt(p)+"" === p){
                return <ListPage />
              }
                // return <Page404 />
                return <Redirect to="/404" />
            }}
           />
    	  <Route component={Page404} />
      </Switch>
      
  </div>
}

function Nav() {
    return <nav>
      {/* <a href="/">首页</a> */}
      <Link to="/">首页</Link>
      <span> | </span>
      {/* <a href="/about">关于</a> */}
      <Link to="/about">关于</Link>
      <span> | </span>
      <Link to="/about/details">关于-详情</Link>
      <span> | </span>
      <Link to="/about/details">列表</Link>
      <span> | </span>
      {/* <Link to="https://wwww.baidu.com">百度</Link> */}
      <a href="https://wwww.baidu.com">百度</a>
    </nav>
}

Link 组件

import { Link } from "react-router-dom";
function Nav() {
    return <nav>
      {/* <a href="/">首页</a> */}
      <Link to="/">首页</Link>
      <span> | </span>
      {/* <a href="/about">关于</a> */}
      <Link to="/about">关于</Link>
      <span> | </span>
      <Link to="/about/details">关于-详情</Link>
      <span> | </span>
      {/* <Link to="https://wwww.baidu.com">百度</Link> */}
      <a href="https://wwww.baidu.com">百度</a>
    </nav>
}

export default Nav;

四、react-redux

组件对 state 的使用或者修改

connect 高级组件

// let withRedux = connect(state=>({count:state.count})); // connect,可以接收一个类型为函数的参数,该函数作用为截取 state ,最终 withRedux 只会将截取出来的部分传递给组件。注意:该函数的返回值必须为对象
// export default withRedux(App);
export default connect((state)=>({count:state.count}))(App);

hooks组件

  • useDispatch

    const dispatch = useDispatch();

  • useSelector

    const todos = useSelector(state => state.todos);

  • useStore

createStore

import { createStore } from "redux";
function reducer(state={count: 1}, action) {
    switch (action.type) {
        case "ADD":
            return {
                count: state.count + 1
            };
    }
    return state;
}
const store = createStore(reducer);

// using
store.dispatch({
    type: "ADD",
});

理解 Redux 核心几个概念与它们之间的关系

  • state 状态

  • reducer 纯函数 - 提供修改状态的方法

    • 纯函数:

      • 相同的输入永远返回相同的输出

      • 不修改函数的输入值

      • 不依赖外部环境状态,只依赖于自己的参数

      • 无任何副作用: DOM操作、异步程序

  • store 仓库 - 管理状态

  • dispatch: ƒ dispatch(action) 发起一个 action

    • 调用 dispatch 之后,dispatch 会将通知 store 执行 reducer,并将 action 传递给 reducer

    • dispatch 是同步方法

  • getState: ƒ getState() 获取状态

  • replaceReducer: ƒ replaceReducer(nextReducer)

    <button onClick={()=>{
              store.replaceReducer((state={count:1},action)=>{
                switch (action.type) {
                  case "ADD":
                    return {
                      count: state.count + 2
                    }; //reducer 的返回值,是修改后的状态
                  case "MIUS":
                    return {
                      count: state.count - 2
                    }
                }
                return state;
              })
          }}>替换reducer</button>
    
  • subscribe: ƒ subscribe(listener)

    const store = createStore(reducer);
    
    render();
    
    let unSubscribe = store.subscribe(()=>{
    
     render();
    
    }); //监听state发生变化
    
  • action 动作 - 对 state 的修改动作

    • action 就是一个 JS 的普通对象

    • action 必须有一个 type 属性,type 属性中,描述了要对 state 做何种修改

    • 潜规则:action的type 必须大写

reducer 的拆分与合并

import { createStore, combineReducers } from "redux";

function count(count=1, action) {
    switch (action.type) {
        case "COUNT_ADD":
            return count + 1;
    }
    return count;
}

function todos(todos=[], action) {
    switch(action.type) {
        case "TODOS_ADD":
            return [
                ...todos,
                {
                    id: Date.now(),
                    todo: action.todo
                }
            ]
    }
    return todos;
}

export default createStore(combineReducers({
    count,
    todos
}));

异步请求

  • 异步 action

  • dispatch(action) --> reducer

  • dispatch(异步action) --> 请求数据 --> dispatch(同步)-->reducer

  • 使用了 Redux Thunk 中间件之后,dispatch 可以接收一个类型为函数的 action

  • 对象 action:会直接调用 reducer 修改

  • 函数 action:会执行这个函数

    function List() { const {data,loading} = useSelector(state=>state.list); const dispatch = useDispatch(); useEffect(()=>{ // dispatch({ // type: "LIST_LOAD", // data:[{ // id:1, // title:1 // }] // }) dispatch(function(dispatch,getState) { console.log("正在发起数据请求"); fetch('http://localhost:8080/api/topics').then(res=>res.json()) .then(res=>{
    dispatch({ type: "LIST_LOAD", data:res.data }) console.log("数据请求成功"); }) }) },[]) return

      {loading ? "数据请求中" :data.map(item=>
    • {item.title}
    • ) }
    }

    // store.js // 处理列表状态 function list(list={ data:[], loading:true },action) { switch (action.type) { case "LIST_LOAD": return { data:action.data, loading: false } } return list; }