React基础

脚手架工具 

1. create react app     

快速生成空的react项目,采取了最简策略 选取的package基本是react项目必须的

2.rekit       

整合了更多的最佳实践

feature orientated architecture 开发大型项目的架构实践   把大项目的复杂度拆分成一个个小的feature  每个feature尽量保持独立 互相之间分耦合  降低项目的复杂度  使项目可扩展可维护

IDE 帮助更好的组织源代码及代码生成工作

3.codesandbox.io在线开发平台

打包

1.打包时设置环境变量为production

 

2.根据环境变量决定哪些代码需要在生产环境运行

rudux-logger中间件 用于在控制台输出所有action的信息   ---只在开发环境下有用

在打包时,该条件下的代码不会被打包进去

3.应用程序的根路径

 

React项目技术栈

react

redux    状态管理

react/router    路由管理

babel   把最新的js特性翻译成浏览器能兼容的旧js语法

webpack   打包

eslink    语法检查

 

对应的生态圈    npm 对应多个小的package

---》需要了解这些包  以及如何进行配置

 

设计模式:

Flux架构:单向数据流     

衍生状态管理框架  Redux Mobx

受控组件与非受控组件

   

组件数据状态管理原则(dry--不要做重复的数据状态保存)

    能计算得到的状态不要单独存储

    组件尽量无状态,所需数据通过props获取

 

JSX语法糖

    

生命周期及其使用场景

   

constructor   初始化内部状态  可以直接修改state

getDerivedStateFromProps   state需要从props初始化时使用(尽量不要用)  每次render都会调用

shouldComponentUpdate  状态变化时是否更新组件   可控的     返回false则不更新组件   使用场景: 用于性能优化

getSnapshotBeforeUpdate   在render前调用(state已更新)   使用场景:获取render之前的DOM状态   

      例如:每秒弹出新消息提醒  想看历史消息时会滚动条会自动滚动     可以在getSnapshotBeforeUpdate 获取节点的滚动高度  在componentDidUpdate时 计算更新的滚动高度 主动改变scrollTop的值   新消息来的时候无缝查看旧消息值

componentWillUnmount   使用场景:释放资源时使用

 

虚拟DOM

     diff算法

设计模式1--高阶组件

接受组件作为参数,对已有组件进行封装,一般不会有新的UI,只是对其封装的组件提供额外的功能

设计模式2--函数作为子组件

应用场景:组件render什么内容取决于使用者     让外部告诉如何显示

新增children属性

由外部决定的内容区域

使用:

context API及其使用场景

解决问题:组件间通信通过props一层层通信麻烦,可通过context API共享全局状态

 

 

 

HOOKS

规则:

1.只在最顶层使用hooks
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。这样才能确保 Hook 在每一次渲染中都按照同样的顺序被调用,从而让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。

2.不要在普通的 JavaScript 函数中调用 Hook。可以:

  • ✅ 在 React 的函数组件中调用 Hook
  • ✅ 在自定义 Hook 中调用其他 Hook

—————————————————————————————————————————————————————————

useState

const [state, setState] = useState(initialState);

setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。

惰性初始 state

initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
}

函数式更新

<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>

注意

useState 不会自动合并更新对象。可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

setState(prevState => {
  // 也可以使用 Object.assign
  return {...prevState, ...updatedValues};
});

useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象。

跳过 state 更新

调用 State Hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较算法 来比较 state。)

需要注意的是,React 可能仍需要在跳过渲染前渲染该组件。不过 React 不会对组件树的“深层”节点进行不必要的渲染。如果你在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化

 

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

使用场景:

state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。使用 useReducer 能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

指定初始 state

将初始 state 作为第二个参数传入 useReducer :

  const [state, dispatch] = useReducer(
    reducer,
    {count: initialCount}  );

惰性初始化

你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)

这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利:

function init(initialCount) {  return {count: initialCount};}
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':      return init(action.payload);    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);  
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>        
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

—————————————————————————————————————————————————————————

useEffect Hook 

在函数组件中执行副作用操作

默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。

使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除

在函数组件中执行副作用操作,数据获取、设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。

可看做 componentDidMountcomponentDidUpdate 和 componentWillUnmount 的组合。

 // 类似于 componentDidMount and componentDidUpdate:
 // 在第一次渲染之后和每次更新之后都会执行。
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
//仅在组件挂载和卸载时执行     
//这就告诉React  effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。
//effect 内部的 props 和 state 就会一直拥有其初始值
useEffect(() => {
       document.title = `You clicked ${count} times`;
  },[]);

无需清除的 effect

只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。

 

与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕  ??

原理?

 

需要清除的 effect

一些副作用是需要清除的。例如订阅外部数据源。清除副作用可以防止引起内存泄露!

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    //清除副作用 为防止内存泄漏,清除函数会在组件卸载前执行  componentWillUnmount 
   return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

可以像使用多个 state 的 Hook 一样,可以使用多个 effect。将不相关逻辑分离到不同的 effect 中:

const Counter=(props)=> {
 useEffect(() => {    
      document.title = `You clicked ${count} times`;
  });

 
  useEffect(() => {    
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}

Hook 允许按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。

 为什么每次更新的时候都要运行 Effect

例如显示好友是否在线的 FriendStatus 组件。从 class 中 props 读取 friend.id,然后在组件挂载后订阅好友的状态,并在卸载组件的时候取消订阅:

  componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() {
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

但是当组件已经显示在屏幕上时,friend prop 发生变化时会发生什么? 我们的组件将继续展示原来的好友状态。这是一个 bug。而且我们还会因为取消订阅时使用错误的好友 ID 导致内存泄露或崩溃的问题。在 class 组件中,我们需要添加 componentDidUpdate 来解决这个问题。

React 在执行当前 effect 之前对上一个 effect 进行清除。这将助于避免 bug以及如何在遇到性能问题时跳过此行为

 

通过跳过 Effect 进行性能优化

在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。(让它 在只有某些值改变的时候 才执行)在 class 组件中,可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

这是很常见的需求,被内置到了 useEffect 的 Hook API 中。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, []); // 传入[],effect内部的 props 和 state 就会一直持有其初始值(仅会在初始的时候执行一次)

对于有清除操作的 effect 同样适用:

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅

注意:

要使用此优化方式,要确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。

参阅文档,了解更多关于如何处理函数以及数组频繁变化时的措施内容。

我们推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

effect 的执行时机

在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作。

然而,并非所有 effect 都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同。

useEffect 会在浏览器绘制后延迟执行,但会在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect。

 

useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。在浏览器执行绘制之前useLayoutEffect 内部的更新计划将被同步刷新。

使用场景:

用来读取 DOM 布局并同步触发重渲染。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

提示

推荐开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect

如果使用服务端渲染,无论 useLayoutEffect 还是 useEffect 都无法在 Javascript 代码加载完成之前执行

服务端渲染组件中引入 useLayoutEffect 代码时会触发 React 告警。解决这个问题,需要将代码逻辑移至 useEffect 中(如果首次渲染不需要这段逻辑的情况下),或是将该组件延迟到客户端渲染完成后再显示(如果直到 useLayoutEffect 执行之前 HTML 都显示错乱的情况下)。

若要从服务端渲染的 HTML 中排除依赖布局 effect 的组件,可以通过使用 showChild && <Child /> 进行条件渲染,并使用 useEffect(() => { setShowChild(true); }, []) 延迟展示组件。这样,在客户端渲染完成之前,UI 就不会像之前那样显示错乱了。

useContext

const value = useContext(MyContext);

React.createContext 解决的问题:

Context 通过组件树提供了一个传递数据的方法,从而避免在每一个层级手动的传递 props 属性

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

 

useContext 的参数必须是 context 对象本身

  • 正确: useContext(MyContext)
  • 错误: useContext(MyContext.Consumer)
  • 错误: useContext(MyContext.Provider)

调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,可通过使用 memoization 来优化

提示

useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>

useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context。

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

—————————————————————————————————————————————————————————

useMemo

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

返回一个 memoized 值(将函数返回值缓存起来 后续执行时若已存在直接读取的) 

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

传入 useMemo 的函数会在渲染期间执行。不要在这个函数内部执行与渲染无关的操作,比如副作用这类的操作

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

useMemo 只是一种性能优化的手段,不要把它当成语义上的保证。

React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 ,之后再添加 useMemo,以达到优化性能的目的。

注意

依赖项数组不会作为参数传给“创建”函数。

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

返回一个 memoized 回调函数。

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

使用场景:

当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时使用。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

注意

依赖项数组不会作为参数传给回调函数。

 

useRef

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。

常见用例--命令式地访问子组件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

本质上,useRef 就像是可以在其 .current 属性中保存一个可变值的“盒子”。

ref 是访问 DOM 的主要方式。如果你将 ref 对象以 <div ref={myRef} /> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性为相应的 DOM 节点

useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,类似于在 class 中使用实例字段的方式。

ref创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: ...} 对象的区别是,useRef 会在每次渲染时返回同一个 ref 对象。

注意

当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:

function FancyInput(props, ref) {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));

  return <input ref={inputRef} ... />;
}

FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={inputRef} /> 的父组件可以调用 inputRef.current.focus()

useDebugValue

useDebugValue(value)

useDebugValue 可用于在 React 开发者工具中显示自定义 hook 的标签。

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 在开发者工具中的这个 Hook 旁边显示标签  // e.g. "FriendStatus: Online"  
  useDebugValue(isOnline ? 'Online' : 'Offline');
  return isOnline;
}

提示

不推荐向每个自定义 Hook 添加 debug 值。当它作为共享库的一部分时才最有价值。

延迟格式化 debug 值

在某些情况下,格式化值的显示可能是一项开销很大的操作。除非需要检查 Hook,否则没有必要这么做。

useDebugValue 可接受一个格式化函数作为可选的第二个参数。该函数只有在 Hook 被检查时才会被调用。它接受 debug 值作为参数,并且会返回一个格式化的显示值。

例如,一个返回 Date 值的自定义 Hook 可以通过格式化函数来避免不必要的 toDateString 函数调用:

useDebugValue(date, date => date.toDateString());

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值