React - 图1
https://react.docschina.org/docs/introducing-jsx.html

简述

React起源于 FaceBook 的内部项目,于2013年在github上开源。React用于构建用户界面的 JavaScript 库,具有声明式、组件化等优点。

视图渲染

构建视图一直是 React 的重点,从 createElement 到 JSX;

createElement

  1. React.createElement(
  2. type,
  3. [props],
  4. [...children]
  5. )
  6. // 示例
  7. let h1 = React.createElement("h1", null, "Hello React");
  8. let p = React.createElement("p", null, "欢迎大家学习 React");
  9. let header = React.createElement("header", null, [h1,p]);
  10. ReactDOM.render(
  11. header,
  12. document.querySelector("#root");
  13. );
  14. // 最终效果如下:
  15. <header>
  16. <h1>Hello React</h1>
  17. <p>欢迎大家学习 React</p>
  18. </header>

使用 JSX 编写的代码将会被转换成使用 React.createElement() 的形式。如果使用了 JSX 方式,那么一般来说就不需要直接调用 React.createElement()。

JSX语法

JSX 是 JS 的语法扩展,但是浏览器并不识别这些扩展,所以需要借助 babel.js 来对 JSX 进行编译,使其成为浏览器识别的语法。

  1. const element = <h1>Hello, world!</h1>;

这个有趣的标签语法既不是字符串也不是 HTML。
React - 图2


Component(组件)

在 React 中提倡组件化开发,使用 React 编写项目时,会把视图抽象为一个组件,最终使用这些组件组装成开发者想要的视图。React中编写组件的方式,一种是类组件,另一种是函数式组件。

注意:

  • 为了区分标签和组件,标签一点要全小写,组件首字母全大写。

创建方式

类组件

React 类组件必须继承 React.Component,并且必须有 render 方法,在 render 方法中的 return 中定义要渲染的视图。

  1. class App extends React.Components {
  2. render(){
  3. return <div>创建类组件</div>
  4. }
  5. }

函数式组件

  1. function Todo(props) {
  2. return <li>Hello, 图雀</li>;
  3. }

props

React 为组件提供了 Props,使得在使用组件时,可以给组件传入属性进行个性化渲染。

类组件中使用 Props

类组件中基本和函数式组件中的 Props 保持一致,除了是通过 this.props 来获取父组件传递下来的属性内容:

  1. class Todo extends React.Component {
  2. render() {
  3. return <li>Hello, {this.props.content}</li>;
  4. }
  5. }
  6. <Todo content="图雀" />

函数式组件中使用 Props

函数式组件默认接收 props 参数,它是一个对象,用于保存父组件传递下来的内容:

  1. function Todo(props) {
  2. return (
  3. <li>Hello, {props.content}</li>
  4. )
  5. }
  6. <Todo content="图雀" />

子组件->父组件

利用 props callback 通信,父组件传递一个 callback 到子组件,当事件触发时将参数放置到 callback 带回给父组件.

  1. // 父组件
  2. callback = (value) => {
  3. // 此处的value便是子组件带回
  4. this.setState({
  5. info: value,
  6. })
  7. }
  8. <div>
  9. <Son callback={this.callback} />
  10. </div>
  11. // 子组件(Son)
  12. handleChange = (e) => {
  13. // 在此处将参数带回父组件
  14. this.props.callback(e.target.value)
  15. }
  16. <input type='text' onChange={this.handleChange} />

PropTypes 进行类型检查

注意:

  • 自 React v15.5 起,React.PropTypes 已移入另一个包中。请使用 prop-types库 代替。
  • 我们提供了一个 codemod 脚本来做自动转换。

https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html

props.children(与Vue插槽类似)

每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容。例如:

  1. // 插槽
  2. <Welcome>
  3. <div>Hello world!</div>
  4. <div>666</div>
  5. </Welcome>
  6. function Welcome(props) {
  7. return <div>{props.children}</div>;
  8. }
  9. // 具名插槽
  10. <Welcome>
  11. {
  12. {
  13. left: <span>左</span>,
  14. right: <span>右</span>
  15. }
  16. }
  17. </Welcome>
  18. function Welcome(props) {
  19. return (
  20. <div>
  21. <div>{props.children.left}</div>
  22. <div>中间内容</div>
  23. <div>{props.children.right}</div>
  24. </div>
  25. )
  26. }

context(跨组件通信)

组件层级嵌套比较深的情况下,传递数据将会特别麻烦;

  1. const ThemeContext = React.createContext(null)
  2. const ThemeProvider = ThemeContext.Provider //提供者
  3. const ThemeConsumer = ThemeContext.Consumer // 订阅消费者
  4. // 爷组件
  5. <ThemeContext.Provider value={{info: "我是info"}}>
  6. <div>
  7. <Son />
  8. </div>
  9. </ThemeContext.Provider>
  10. // 孙组件(Son)
  11. <ThemeContext.Consumer>
  12. {(value) => (
  13. // 通过Consumer直接获取父组件的值
  14. <div>
  15. <p>爷组件的值:{value.info}</p>
  16. </div>
  17. )}
  18. </ThemeContext.Consumer>

很多优秀的 React 组件的核心功能都通过 Context 来实现的,比如 react-redux 和 react-router 等,所以掌握 Context 是必须的。


State

注意:

  • 只需要传入要修改的状态,不需要传入所有状态,setState会自动进行合并。
  • setState 是一个异步方法。
  • 多个 setState 会被合并,但只会引起一次视图渲染(render)。

类组件中使用 State

定义 、使用 、更新

  1. constructor(props) {
  2. super(props);
  3. this.state = {
  4. todoList: ["图雀", "图雀写作工具", "图雀社区", "图雀文档"]
  5. };
  6. }
  7. /*
  8. 这里 constructor 方法接收的 props 属性就是我们在上一节中讲到的那个 props;
  9. 并且 React 约定每个继承自 React.Component 的组件在定义 constructor 方法
  10. 时,要在方法内首行加入 super(props) 。
  11. */
  1. constructor(props) {
  2. super(props);
  3. this.state = {
  4. todoList: ["图雀", "图雀写作工具", "图雀社区", "图雀文档"]
  5. };
  6. }
  7. render() {
  8. return (
  9. <div>{this.state.todoList[0]}</div>
  10. );
  11. }
  12. // 通过 this.state 的方式来使用 state;
  1. this.setState({ todoList: [1,2,3] });
  2. // 通过 this.setState 方法来更新 state,它会把新的 state 和旧的 state 进行合并;

函数式组件中使用 State

Hooks 中的 useState(),下面的 Hooks 章节中会进行介绍。

Refs

开发项目时,会遇到一些特殊的需求,需要使用原生 DOM 节点。比如让文本框获得焦点。

注意:

  • ref的命名虽然可以自定义,但也要注意 JS 的命名规范,另外要遵循驼峰命名法;
  • 单个组件内,ref不能重名;
  • 获取 ref 时要在 componentDidMount 和 componentDidUpdate 中进行,否则 ref 是还没有赋值或还没更新的。
  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。

string ref

如果你之前使用过 React,你可能了解过之前的 API 中的 string 类型的 ref 属性,例如 “textInput”。你可以通过 this.refs.textInput 来访问 DOM 节点。我们不建议使用它,因为 string 类型的 refs 存在 一些问题。它已过时并可能会在未来的版本被移除。

createRef

  1. class Child extends Component {
  2. render(){
  3. return <p>子组件内容</p>
  4. }
  5. }
  6. class App extends Component {
  7. perent = createRef();
  8. child = createRef();
  9. componentDidMount(){
  10. console.log(this.parent.current); // 打印真实的 DOM 节点
  11. console.log(this.child.current); // 打印 Child 的实例化对象
  12. }
  13. render(){
  14. <p ref={this.perent}>父组件内容</p>
  15. <Child ref={this.child} />
  16. }
  17. }

其它

dangerouslySetInnerHTML

  • dangerouslySetInnerHTML 是 React 为浏览器 DOM 提供 innerHTML 的替换方案;
  • 使用dangerouslySetInnerHTML属性的虚拟dom元素之间不能有内容,否则会报错。 ```jsx function createMarkup() { return {__html: “createMarkup“}; }

function MyComponent() { return

; }

  1. ---
  2. <a name="uHgU4"></a>
  3. # 高阶组件(HOC)
  4. 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。**高阶组件是参数为组件,返回值为新组件的函数。**
  5. <a name="NCthq"></a>
  6. ## 两种不同的高阶组件
  7. 常用的高阶组件有**属性代理**和**反向继承**两种,两者之间有一些共性和区别。接下来分别介绍一下两种模式下的高阶组件。
  8. <a name="XmkXU"></a>
  9. ### 属性代理
  10. 属性代理,就是用组件包裹一层代理组件,在代理组件上,可以做一些,对源组件的强化操作。这里注意属性代理返回的是一个新组件,被包裹的原始组件,将在新的组件里被挂载。
  11. ```javascript
  12. function HOC(WrapComponent){
  13. return class Advance extends React.Component{
  14. state={
  15. name:'alien'
  16. }
  17. render(){
  18. return <WrapComponent { ...this.props } { ...this.state } />
  19. }
  20. }
  21. }

优点:

  • ① 属性代理可以和业务组件低耦合,零耦合,对于条件渲染和 props 属性增强,只负责控制子组件渲染和传递额外的 props 就可以了,所以无须知道,业务组件做了些什么。所以正向属性代理,更适合做一些开源项目的 HOC ,目前开源的 HOC 基本都是通过这个模式实现的。
  • ② 同样适用于类组件和函数组件。
  • ③ 可以完全隔离业务组件的渲染,因为属性代理说白了是一个新的组件,相比反向继承,可以完全控制业务组件是否渲染。
  • ④ 可以嵌套使用,多个 HOC 是可以嵌套使用的,而且一般不会限制包装 HOC 的先后顺序。

缺点:

  • ① 一般无法直接获取原始组件的状态,如果想要获取,需要 ref 获取组件实例。
  • ② 无法直接继承静态属性。如果需要继承需要手动处理,或者引入第三方库。
  • ③ 因为本质上是产生了一个新组件,所以需要配合 forwardRef 来转发 ref。

    反向继承

    反向继承和属性代理有一定的区别,在于包装后的组件继承了原始组件本身,所以此时无须再去挂载业务组件。

    1. class Index extends React.Component{
    2. render(){
    3. return <div> hello,world </div>
    4. }
    5. }
    6. function HOC(Component){
    7. return class wrapComponent extends Component{ /* 直接继承需要包装的组件 */
    8. }
    9. }
    10. export default HOC(Index)

    优点:

  • ① 方便获取组件内部状态,比如 state ,props ,生命周期,绑定的事件函数等。

  • ② es6继承可以良好继承静态属性。所以无须对静态属性和方法进行额外的处理。

缺点:

  • ① 函数组件无法使用。
  • ② 和被包装的组件耦合度高,需要知道被包装的原始组件的内部状态,具体做了些什么?
  • ③ 如果多个反向继承 HOC 嵌套在一起,当前状态会覆盖上一个状态。这样带来的隐患是非常大的,比如说有多个 componentDidMount ,当前 componentDidMount 会覆盖上一个 componentDidMount 。这样副作用串联起来,影响很大。

    高阶组件功能说明

    强化props、控制渲染(渲染劫持、动态加载)、组件赋能(ref获取实例、事件监听)

    高价组件注意事项

  • 谨慎修改原型链

  • 不要在函数组件内部或类组件render函数中使用HOC
  • ref的处理
  • 注意多个HOC嵌套顺序问题
  • 继承静态属性

    进阶实践-权限拦截

    第一步,在根部注入权限。

    ```jsx export const Permission = React.createContext([])

export default function Index(){ const [ rootPermission , setRootPermission ] = React.useState([]) React.useEffect(()=>{ / 获取权限列表 / getRootPermission().then(res=>{ const { code , data } = res as any code === 200 && setRootPermission(data) // [ ‘docList’ , ‘tagList’ ] }) },[]) return }

  1. <a name="zdboa"></a>
  2. ### 第二步:重点编写HOC
  3. ```jsx
  4. /* 没有权限 */
  5. function NoPermission (){
  6. return <div>您暂时没有权限,请联系管理员开通权限!</div>
  7. }
  8. /* 编写HOC */
  9. export function PermissionHoc(authorization){
  10. return function(Component){
  11. return function Home (props){
  12. const matchPermission =(value,list)=> list.indexOf(value) /* 匹配权限 */
  13. return <Permission.Consumer>
  14. {
  15. (permissionList) => matchPermission(authorization,permissionList) >= 0 ? <Component {...props} /> : <NoPermission />
  16. }
  17. </Permission.Consumer>
  18. }
  19. }
  20. }

第三部:绑定权限

  1. @PermissionHoc('writeDoc') // 绑定文档录入页面
  2. export default class Index extends React.Component{}
  3. export default PermissionHoc('writeTag')(index) //绑定标签录入页面
  4. export default PermissionHoc('tagList')(index) //绑定标签列表页面
  5. export default PermissionHoc('docList')(Index) // 绑定文档列表页面

生命周期

image.png
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
class 中生命周期函数经常包含不相关的逻辑,使用 Hook 就可以解决这一个问题。


Hooks

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数(Hook 就是 JavaScript 函数)。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

注意:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断、其他 JavaScript 函数中、子函数中调用。
  • 只能在 React 的函数组件 中调用 Hook。
  • 在自定义 Hook 中调用其他 Hook。

基础 Hook

useState(状态)

注意:

  • 不会把新的 state 和旧的 state 进行合并;
  • 返回一对值:当前状态和一个让你更新它的函数
  1. function Example(){
  2. let [count, setCount] = useState(0);
  3. return (
  4. <div>
  5. <Box>
  6. <div>count:{count}</div>
  7. <button onClick={()=>setCount(count+1)}>count++</button>
  8. </Box>
  9. </div>
  10. )
  11. }

useEffect(副作用)

它跟 class 组件中的生命周期, componentDidMount(挂载时)componentDidUpdate(更新时)componentWillUnmount(卸载时) 具有相同的用途,只不过被合并成了一个 API。

注意:

  • 跟 useState 一样,你可以在组件中多次使用 useEffect ;

函数(第一个参数)

  1. import React, { useState, useEffect } from 'react';
  2. function Example() {
  3. const [count, setCount] = useState(0);
  4. // Similar to componentDidMount and componentDidUpdate:
  5. useEffect(() => {
  6. // Update the document title using the browser API
  7. document.title = `You clicked ${count} times`;
  8. });
  9. return (
  10. <div>
  11. <p>You clicked {count} times</p>
  12. <button onClick={() => setCount(count + 1)}>
  13. Click me
  14. </button>
  15. </div>
  16. );
  17. }

需要清除的 effect

如果你的 effect 返回 一个 函数, React 会在组件卸载的时候调用它。

  1. useEffect(() => {
  2. // ......
  3. return function cleanup() {
  4. // 在组件卸载的时候调用它
  5. };
  6. });

数组(第二个参数)

可以传入 有数据的数组空数组。当传入 有数据的数组 时,此数据将会被监听,一旦监听的数据发生改变, Effect 将会被执行;传入 空数组 时,因为没有数据被监听, Effect 只会执行一次。

  1. // 有数据的数组
  2. let [count, setCount] = useState();
  3. useEffect(() => {
  4. // ......
  5. }, [count]);
  6. // 空数组
  7. useEffect(() => {
  8. // ......
  9. }, []);

useContext

useContext 让你不使用组件嵌套就可以订阅 React 的 Context。接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 的 value prop 决定。

  1. const themes = {
  2. light: {
  3. foreground: "#000000",
  4. background: "#eeeeee"
  5. },
  6. dark: {
  7. foreground: "#ffffff",
  8. background: "#222222"
  9. }
  10. };
  11. const ThemeContext = React.createContext(themes.light);
  12. function App() {
  13. return (
  14. <ThemeContext.Provider value={themes.dark}>
  15. <Toolbar />
  16. </ThemeContext.Provider>
  17. );
  18. }
  19. function Toolbar(props) {
  20. return (
  21. <div>
  22. <ThemedButton />
  23. </div>
  24. );
  25. }
  26. function ThemedButton() {
  27. const theme = useContext(ThemeContext);
  28. return (
  29. <button style={{ background: theme.background, color: theme.foreground }}>
  30. I am styled by theme context!
  31. </button>
  32. );
  33. }

额外的 Hook API

以下介绍的 Hook,有些是上一节中基础 Hook 的变体,有些则仅在特殊情况下会用到。不用特意预先学习它们。

useReducer - 独立状态管理

是React提供的一个高级Hook,它不像useEffect、useState、useRef等必须hook一样,没有它我们也可以正常完成需求的开发,但 useReducer 可以使我们的代码具有更好的可读性可维护性可预测性

  1. const initialState = {count: 0};
  2. function reducer(state, action) {
  3. switch (action.type) {
  4. case 'increment':
  5. return {count: state.count + 1};
  6. case 'decrement':
  7. return {count: state.count - 1};
  8. default:
  9. throw new Error();
  10. }
  11. }
  12. function Counter() {
  13. const [state, dispatch] = useReducer(reducer, initialState);
  14. return (
  15. <>
  16. Count: {state.count}
  17. <button onClick={() => dispatch({type: 'decrement'})}>-</button>
  18. <button onClick={() => dispatch({type: 'increment'})}>+</button>
  19. </>
  20. );
  21. }
  1. // useState版login
  2. function LoginPage() {
  3. const [name, setName] = useState(''); // 用户名
  4. const [pwd, setPwd] = useState(''); // 密码
  5. const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
  6. const [error, setError] = useState(''); // 错误信息
  7. const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录
  8. const login = (event) => {
  9. event.preventDefault();
  10. setError('');
  11. setIsLoading(true);
  12. login({ name, pwd })
  13. .then(() => {
  14. setIsLoggedIn(true);
  15. setIsLoading(false);
  16. })
  17. .catch((error) => {
  18. // 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
  19. setError(error.message);
  20. setName('');
  21. setPwd('');
  22. setIsLoading(false);
  23. });
  24. }
  25. return (
  26. // 返回页面JSX Element
  27. )
  28. }
  29. // useReducer版login
  30. const initState = {
  31. name: '',
  32. pwd: '',
  33. isLoading: false,
  34. error: '',
  35. isLoggedIn: false,
  36. }
  37. function loginReducer(state, action) {
  38. switch(action.type) {
  39. case 'login':
  40. return {
  41. ...state,
  42. isLoading: true,
  43. error: '',
  44. }
  45. case 'success':
  46. return {
  47. ...state,
  48. isLoggedIn: true,
  49. isLoading: false,
  50. }
  51. case 'error':
  52. return {
  53. ...state,
  54. error: action.payload.error,
  55. name: '',
  56. pwd: '',
  57. isLoading: false,
  58. }
  59. default:
  60. return state;
  61. }
  62. }
  63. function LoginPage() {
  64. const [state, dispatch] = useReducer(loginReducer, initState);
  65. const { name, pwd, isLoading, error, isLoggedIn } = state;
  66. const login = (event) => {
  67. event.preventDefault();
  68. dispatch({ type: 'login' });
  69. login({ name, pwd })
  70. .then(() => {
  71. dispatch({ type: 'success' });
  72. })
  73. .catch((error) => {
  74. dispatch({
  75. type: 'error'
  76. payload: { error: error.message }
  77. });
  78. });
  79. }
  80. return (
  81. // 返回页面JSX Element
  82. )
  83. }
  1. // 定义初始化值
  2. const initState = {
  3. name: '',
  4. pwd: '',
  5. isLoading: false,
  6. error: '',
  7. isLoggedIn: false,
  8. }
  9. // 定义state[业务]处理逻辑 reducer函数
  10. function loginReducer(state, action) {
  11. switch(action.type) {
  12. case 'login':
  13. return {
  14. ...state,
  15. isLoading: true,
  16. error: '',
  17. }
  18. case 'success':
  19. return {
  20. ...state,
  21. isLoggedIn: true,
  22. isLoading: false,
  23. }
  24. case 'error':
  25. return {
  26. ...state,
  27. error: action.payload.error,
  28. name: '',
  29. pwd: '',
  30. isLoading: false,
  31. }
  32. default:
  33. return state;
  34. }
  35. }
  36. // 定义 context函数
  37. const LoginContext = React.createContext();
  38. function LoginPage() {
  39. const [state, dispatch] = useReducer(loginReducer, initState);
  40. const { name, pwd, isLoading, error, isLoggedIn } = state;
  41. const login = (event) => {
  42. event.preventDefault();
  43. dispatch({ type: 'login' });
  44. login({ name, pwd })
  45. .then(() => {
  46. dispatch({ type: 'success' });
  47. })
  48. .catch((error) => {
  49. dispatch({
  50. type: 'error'
  51. payload: { error: error.message }
  52. });
  53. });
  54. }
  55. // 利用 context 共享dispatch
  56. return (
  57. <LoginContext.Provider value={dispatch}>
  58. <...>
  59. <LoginButton />
  60. </LoginContext.Provider>
  61. )
  62. }
  63. function LoginButton() {
  64. // 子组件中直接通过context拿到dispatch,出发reducer操作state
  65. const dispatch = useContext(LoginContext);
  66. const click = () => {
  67. if (error) {
  68. // 子组件可以直接 dispatch action
  69. dispatch({
  70. type: 'error'
  71. payload: { error: error.message }
  72. });
  73. }
  74. }
  75. }

总结:

  • 如果你的页面state很简单,可以直接使用useState
  • 如果你的页面state比较复杂(state是一个对象或者state非常多散落在各处)请使用userReducer
  • 如果你的页面组件层级比较深,并且需要子组件触发state的变化,可以考虑useReducer + useContext

useCallback - 缓存方法

useMemo - 存储计算结果

useRef - 函数存储容器

  1. function TextInputWithFocusButton() {
  2. const inputEl = useRef(null);
  3. const onButtonClick = () => {
  4. // `current` 指向已挂载到 DOM 上的文本输入元素
  5. inputEl.current.focus();
  6. };
  7. return (
  8. <>
  9. <input ref={inputEl} type="text" />
  10. <button onClick={onButtonClick}>Focus the input</button>
  11. </>
  12. );
  13. }

useImperativeHandle -

useLayoutEffect -

useDebugValue -

自定义Hook

有时候我们会想要在组件之间重用一些状态逻辑。目前为止,有两种主流方案来解决这个问题:高阶组件render props。自定义 Hook 可以让你在不增加组件的情况下达到同样的目的。

注意:

  • 关注分离,提取共同逻辑;
  • 三条规则:use开头、不共享、独立state;
  • useYourImagination();

React-Router

https://reactrouter.com/docs/en/v6


Redux(状态管理)

https://redux.js.org/introduction/getting-started


Mobx

https://cn.mobx.js.org/


DvaJS

https://dvajs.com/


UmiJS

https://umijs.org/zh-CN


Taro

https://taro-docs.jd.com/taro/docs/


React Native

https://reactnative.cn/