React基本用法

# 路由传递参数

<Link to={{
        pathname:"/",
        search:"?lx=1",
        hash:"#AA",
        state: {id: id} 
    }} exact >
     首页
 </Link>
     
1
2
3
4
5
6
7
8
9
  • 1.问号传参 基于location来完成处理 let {data, location: {search}} = this.props

  • 2.地址栏传参基于match的params来完成处理 let {data, match: {params}} = this.prop

  • 3.Link传参 循环进来的,此参数传参在浏览器看不见 let {data, location: {state}} = this.props

  • 参数解析(问号传参)

//两种方式都行
const utils = {
   queryURLParameter(url) {
        let regParam = /([^?&=#]+)=?([^?&=#]+)?/ig,
            obj = {};
        url.replace(regParam, (...arg) => {
            obj[arg[1]] = arg[2];
        });
        return obj;
   },
   queryStringToJson(queryString) {
        if (queryString.indexOf('?') > -1) {
            queryString = queryString.split('?')[1]
        }
        const pairs = queryString.split('&');
        const result = {};
        pairs.forEach(pair => {
            pair = pair.split('=');
            result[pair[0]] = decodeURIComponent(pair[1] || '')
        });
        return result
   }
        
};
export default utils;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 发起 AJAX 请求

我们应当将AJAX 请求放到 componentDidMount 函数中执行,主要原因有下

  • componentsDidMount里面请求数据此时dom已经渲染上去,从用户友好角度来讲,我们更愿意让用户先看到一个没有数据的方式,再通过一个spin的动画,来加载数据;componentsWillMount里面请求数据,拿到数据之后setState的时机是不确定的,可能是render之前,也可能是render之后,并不是下一个时间段,这依赖于ajax的返回时间,所以不能准时的出现loading图,所以说会出现较长白屏的现象.
  • 还有在rn中,用react做服务端的同构,或者更高技术的时候会有一系列的问题【componentWillMount时发生在服务端 componentDidMount在客户端,会冲突】。
  • React16 调和算法Fiber会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount

# PropTypes

JavaScript 是弱类型语言,所以请尽量声明 propTypes 对 props 进行校验,以减少不必要的问题。

内置的 prop type 有:

  • PropTypes.array
  • PropTypes.bool
  • PropTypes.func
  • PropTypes.number
  • PropTypes.object
  • PropTypes.string
  • PropTypes.any
  • PropTypes.shape
import PropTypes from "prop-types"

static defaultProps = {
    a: 0,
    b: 0,
};
static propTypes = {
    a: PropTypes.number,
    b: PropTypes.number,
    store: PropTypes.shape({
           subscribe: PropTypes.func.isRequired,
           dispatch: PropTypes.func.isRequired,
           getState: PropTypes.func.isRequired
    }),
    data: PropTypes.array.isRequired//必须有
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

查看更多 (opens new window)

# Hooks为何能使用const 在下面声明的函数

//思想
(function () {
   function b() {
      return a;
   }

  const a = 1;
  return b();
})();
1
2
3
4
5
6
7
8
9

# 组件传递参数

//父
<List onItem={(item) => this.onItem(item)}/>
//子
{list.map((item, index) => {
  return (
    <div className={styles.item} key={index}    
     onClick={() => this.props.onItem(item)}>
   )
}

//父
<List onItem={this.onItem}/>
//子
{list.map((item, index) => {
  return (
    <div className={styles.item} key={index}    
     onClick={() => this.props.onItem(item)}>
   )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# PureComponent原理

shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
  const thisProps = this.props || {}, thisState = this.state || {};

  if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
      Object.keys(thisState).length !== Object.keys(nextState).length) {
    return true;
  }

  for (const key in nextProps) {
    if (!Object.is(thisProps[key], nextProps[key])) {
      return true;
    }
  }

  for (const key in nextState) {
    if (thisState[key] !== nextState[key] || !Object.is(thisState[key], nextState[key])) {
      return true;
    }
  }
  return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 缓存路由

网址 (opens new window)


import React from 'react';
import PropTypes from 'prop-types';
import {matchPath} from 'react-router';
import {Route} from 'react-router-dom';

class RouteCache extends React.Component {

  static propTypes = {
    include: PropTypes.oneOfType([
      PropTypes.bool,
      PropTypes.array
    ])
  };

  cache = {}; //缓存已加载过的组件

  render() {
    const {children, include = []} = this.props;

    return React.Children.map(children, child => {
      if (React.isValidElement(child)) { // 验证是否为是react element
        const {path} = child.props;
        const match = matchPath(location.pathname, {...child.props, path});

        if (match && (include === true || include.includes(path))) {
          //如果匹配,则将对应path的computedMatch属性加入cache对象里
          //当include为true时,缓存全部组件,当include为数组时缓存对应组件
          this.cache[path] = {computedMatch: match};
        }

        //可以在computedMatch里追加入一个display属性,可以在路由组件的props.match拿到
        const cloneProps = this.cache[path] && Object.assign(this.cache[path].computedMatch, {display: match ? 'block' : 'none'});

        return <div style={{display: match ? 'block' : 'none'}}>{React.cloneElement(child, {computedMatch: cloneProps})}</div>;
      }

      return null;
    });
  }
}

// 使用
<RouteCache include={['/login', '/home']}>
  <Route path="/login" component={Login} />
  <Route path="/home" component={App} />
</RouteCache>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 获取实际的DOM

ReactDOM.findDOMNode(this.sildeWrapper).clientWidth;   //废弃  改用ref
1

# 添加DOM

<div dangerouslySetInnerHTML={{__html: '<p>123</p>'}} />
1

# React.memo


 






import PowerList from "./components/PowerList"
const MemoPowerList = memo(PowerList, (prevProps, nextProps) => prevProps.data === nextProps.data);

 render() {
     <MemoPowerList/>
 }

1
2
3
4
5
6
7

# React.createElement

React.createElement(
  type,
  {props},
  [...children]
)
1
2
3
4
5







 
 
 
 
 


// jsx 语法
<div id='one' class='two'>
    <div id="spanOne">this is spanOne</div>
    <h1 id="spanTwo">this is spanTwo</h1>
</div>

// 转化为 js
React.createElement(
    "div", {id: "one", className: "two"},
    React.createElement("div", {id: "spanOne"}, "this is spanOne"),
    React.createElement("h1", {id: "spanTwo"}, "this is spanTwo")
)

1
2
3
4
5
6
7
8
9
10
11
12
13

# React.cloneElement

新的子元素将取代现有的子元素, keyref将被保留

React.cloneElement(
  element,
  {props},
  [...children]
)
1
2
3
4
5

适用于 父组件是独立的,子组件是独立的, 父组件数据改变,想要通知子组件,或者子组件想要改父组件的数据(也是通过回调)














 
 
 
 
 
 
 
 




























import React, {Fragment, Component, useState, useEffect} from 'react';
class MyContainer extends Component {
    state = {count: 1};
    handleClick = () => {
        this.setState(({count}) => {
            return {count: count + 1}
        })
    };

    render() {
        const {count} = this.state;
        return (
            <Fragment>
                {React.Children.map(this.props.children, (item, index) => {
                    return React.cloneElement(item,
                        {
                            parentState: count,
                            handleClick: this.handleClick
                        },
                    )
                })}
            </Fragment>
        )
    }
}
function MySub(props) {
    const {subInfo, parentState, handleClick} = props;
    return (
        <div style={{margin: "15px", border: "1px solid red", padding: 10}}>
            子元素:{subInfo}
            <br/>
            父组件属性count值: {parentState}
            <br/>
            <button onClick={() => handleClick()}>点击+1</button>
        </div>
    )
}
function Index() {
    return (
        <Fragment>
            <MyContainer>
                <MySub subInfo={"第一个"}/>
                <MySub subInfo={"第二个"}/>
            </MyContainer>
        </Fragment>
    )
}
export default Index;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# ReactDOM.createPortal

react 中所有的组件都会位于#app下,而使用Portals提供了一种脱离#app的组件。

适用 模态框,通知,警告,goTop



















 
 
 
 












import React, {Fragment, useState, useEffect} from 'react';
import ReactDOM from "react-dom"

const modalRoot = document.getElementById('modal');

class Modal extends React.Component {
    constructor(props) {
        super(props);
        this.el = document.createElement('div');
    }
    componentDidMount() {
        modalRoot.appendChild(this.el);
        console.log(document.getElementById('root').style.display = 'none');
    }
    componentWillUnmount() {
        modalRoot.removeChild(this.el);
    }
    render() {
        return ReactDOM.createPortal(
            this.props.children,
            this.el,
        );
    }
}
function f() {
    return (
        <Modal>
            妈的 DW
        </Modal>
    )
}
export default f;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# Render Props

就是给组件添加一个值为函数的属性,这个函数可以在组件渲染的时候调用

适用于 父组件是独立的,子组件是独立的, 父组件数据改变,想要通知子组件,或者子组件想要改父组件的数据(也是通过回调)

实现一个图片跟随鼠标的功能

不知道这个功能之前可能这样实现

import React from 'react';
const Cat = ({mouse}) => {
    return (
        <img src="https://ae01.alicdn.com/kf/H62563fe1dc6447aca248634b671b7a59W.png"
             style={{position: 'absolute', left: mouse.x, top: mouse.y}} alt={''}/>
    );
};
class Mouse extends React.Component {
    state = {x: 0, y: 0};
    handleMouseMove = (event) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        }, () => {
            this.props.render(this.state) //在这里传给父组件
        });
    };
    render() {
        return (
            <div style={{height: '100vh'}} onMouseMove={this.handleMouseMove}>
                <h1>移动鼠标</h1>
                <p>当前的鼠标定位 (X:{this.state.x}, Y:{this.state.y})</p>
                {/*TODO:组件在里面渲染*/}
                {this.props.children}  
                
                {/*TODO:这种方式简单*/}
                {/*{*/}
                {/*    React.Children.map(this.props.children, (item, index) => {*/}
                {/*        return React.cloneElement(item,*/}
                {/*            {*/}
                {/*                mouse: this.state*/}
                {/*            },*/}
                {/*        )*/}
                {/*    })*/}
                {/*}*/}
            </div>
        );
    }
}
class Index extends React.Component {
    state = { mouse: {} };  //交给他们的父级来出来,然后重新state,把值传进来
    render() {
        const {mouse} = this.state;
        return (
            <Mouse render={(item) => {
                this.setState({mouse: item})
            }}>
                <Cat mouse={mouse}/>
            </Mouse>
        );
    }
}
export default Index;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

现在可以这么实现

import React from 'react';
const Cat = ({mouse}) => {
    return (
        <img src="https://ae01.alicdn.com/kf/H62563fe1dc6447aca248634b671b7a59W.png"
             style={{position: 'absolute', left: mouse.x, top: mouse.y}} alt={''}/>
    );
};
class Mouse extends React.Component {
    state = {x: 0, y: 0};
    handleMouseMove = (event) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    };
    render() {
        return (
            <div style={{height: '100vh'}} onMouseMove={this.handleMouseMove}>
                <h1>移动鼠标</h1>
                <p>当前的鼠标定位 (X:{this.state.x}, Y:{this.state.y})</p>
                {/*TODO:在render里面执行的这个方法,不仅仅是执行当前这个函数还把函数在这里渲染出来了*/}
                {this.props.render(this.state)}
            </div>
        );
    }
}
const Index = () => {
    const catRender=(item)=><Cat mouse={item}/>;
    return (<Mouse render={catRender}/>);
};
export default Index;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# Error Boundaries

错误边界是一种React组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。 错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

static getDerivedStateFromError(error)

当后代组件抛出错误时,首先会调用这个方法,并将抛出的错误作为参数。无论这个方法返回什么值,都将用于更新组件的状态。

在后代组件抛出错误之后,也会调用componentDidCatch方法除了抛出的错误之外,还会有另一个参数,这个参数包含了有关错误的更多信息

  • getDerivedStateFromError是在reconciliation阶段触发,所以getDerivedStateFromError进行捕获错误后进行组件的状态变更,不允许出现副作用。
  • componentDidCatch因为在commit阶段,因此允许执行副作用。 它应该用于记录错误之类的情况

注意

错误边界无法捕获以下场景中产生的错误:

  • 事件处理(了解更多)
  • 异步代码(例如 setTimeoutrequestAnimationFrame 回调函数)
  • 服务端渲染
  • 它自身抛出来的错误(并非它的子组件)

只有 class 组件才可以成为错误边界组件

自React16起,任何未被错误边界捕获的错误将会导致整个React组件树被卸载。

错误边界的粒度由你来决定,可以将其包装在最顶层的路由组件并为用户展示一个 “Something went wrong” 的错误信息, 就像服务端框架经常处理崩溃一样。 你也可以将单独的部件包装在错误边界以保护应用其他部分不崩溃。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# Code Splitting

import()

适用于函数








 
 







//add.js
export const sum = (a, b) => a + b;

//index.js
import React from 'react';
function Index() {
    const handleClick = async () => {
        const {sum} = await import('./add');
        console.log(sum(1, 2));
    };
    return (
        <button onClick={handleClick}>点击</button>
    );
}
export default Index;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

React.lazy

适用于组件 目前不支持服务端渲染

 


 





const Foo = React.lazy(() => import('../components/Foo'));
render() {
    return (
            <Suspense fallback={<div>loading...</div>}>
                <Foo/>
            </Suspense>
        )
}
1
2
3
4
5
6
7
8

React.lazy 目前只支持默认导出(default exports) 你可以创建一个中间模块,来重新导出为默认模块

//a.jsx
export const A = /* ... */;
export const B = /* ... */;
//middle.js
export { A as default } from "./a";   //A就相当于默认导出
//index.jsx
import React, { lazy } from 'react';
const A = lazy(() => import("./middle"));

<Suspense fallback={<div>loading...</div>}>
    <A/>
</Suspense>
1
2
3
4
5
6
7
8
9
10
11
12

# React.StrictMode

开发模式会调用多次

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告

import React from "react";
function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

StrictMode 目前有助于:

  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告(React.createRef();)
  • 关于使用废弃的 findDOMNode 方法的警告(貌似没有)
  • 检测遗留 context API
  • 检测意外的副作用

react 在检测意外的副作用时可能重复调用某些生命周期方法、hooks 或者 render 方法,其包括:

class 组件的 constructor,render 以及 shouldComponentUpdate 方法 class 组件的生命周期方法 getDerivedStateFromProps 函数组件 状态更新函数 (即 setState 的第一个参数) 函数组件中 useState,useMemo 或者 useReducer 中的函数 当 React 发现在重复调用这些方法时出现了内存泄漏、无限循环或者其他奇怪的表现时会在控制台输出错误,以供程序员快速定位错误。

简单来说就是我们在使用 hooks 或者在某些生命周期函数中不应该使用有副作用的代码。在开发模式的 StrictMode 下,React 会帮助我们发现这些不好的代码并给予提示。

StrictMode 的这个重复调用的特性只使用于开发模式,在生产模式下不会触发多次调用。

# ref(非受控)

React的ref有4种用法:

  • 字符串(已废弃)
  • 回调函数
//获取子组件的dom节点
function CustomTextInput(props) {
    return (
        <div>
            <input ref={props.inputRef} />
        </div>
    );
}

function Parent(props) {
  return (
    <div>
      My input: <CustomTextInput inputRef={props.inputRef} />
    </div>
  );
}

class Grandparent extends React.Component {
  render() {
    return (
      <Parent
        inputRef={el => this.inputElement = el}
      />
    );
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
  • React.createRef() (React16.3提供,无状态的组件也可以使用)

  • Hooks useRef() 用法类似createRef()

const Example = () => {
    let inputRef = useRef(null);
    useEffect(() => {
        inputRef.current.focus();
    }, []);
    return (
        <input type="text" ref={inputRef}/>
    )
};
1
2
3
4
5
6
7
8
9
//类组件
class Child extends React.Component{
    constructor(props){
        super(props);
        this.myRef=React.createRef();
    }
    componentDidMount(){
        console.log(this.myRef.current);
    }
    render(){
        return <input ref={this.myRef}/>
    }
}

//无状态组件
function CustomTextInput(props) {
  // textInput must be declared here so the ref can refer to it
  let textInput = React.createRef();

  function handleClick() {
    textInput.current.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={textInput} />

      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  • React.forwardRef() 同样是React 16.3版本后提供的,可以用来创建子组件,以传递ref。

# React生命周期

# 极简 React 入门配置

https://kentcdodds.com/blog/super-simple-start-to-react

<html>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@16.13.1/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.8.3/babel.js"></script>
<script type="text/babel">
  const {PureComponent} = React;
  // 也可以是函数式 
  class App extends PureComponent {
    render = () => {
      return (
        <div>
          <h1>watermark-webp</h1>
        </div>
      )
    }
  };
  ReactDOM.render(<App />, document.getElementById('root'))
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# antd

@import '~antd/es/style/themes/default.less';

@pro-header-hover-bg: rgba(0, 0, 0, 0.025);
1
2
3
//获取from里面的数据集合
 this.props.form.getFieldsValue()
//重置from输入的内容,(参数不传默认是所有的)
this.props.form.resetFields('search_product_name');
//抛出错误
this.props.form.setFields({
   name: {
      value: val,
        errors: [new Error('forbid ha')],
    },
});
//设置from的值
this.props.form.setFieldsValue({
   points: 1212121,
});
/*
* 提交数据滚动到该位置
* 参数1 只捕捉该字段的数据
* 参数2 定义 validateFieldsAndScroll 的滚动行为
* 参数3 获取数据,错误捕获
*/
form.validateFieldsAndScroll( 
   ['user', 'password'],
    {
      scroll: {
           offsetTop: 60
       }
   },(err, fieldsValue) => {
        if(err)return false;
   }     
)
//自定义输入框额格式 
{getFieldDecorator('user', {
    rules: [
    {required: true, message: '必填'},
    {
        validator: (rule, value, callback) => {
            if ('不符合') {
                callback('不符合')
            }
            callback()
        }
    }
    ],
    initialValue: ''
})(<Input/>)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 定义环境变量

cross-env ANALYZE=true // 设置环境变量
process.env.ANALYZE=true  // 获取环境变量

new webpack.DefinePlugin({
    ENV: JSON.stringify(process.env.ENV), // 可以设置环境变量在页面直接使用
}),
1
2
3
4
5
6

react 默认测试环境 NODE_ENV为development,正式为production

# dva & effects

//全局执行model
window.g_app 

//effects基本用法
*add(action,{put,call,select}){
    yield call(delay,1000);//yield 调用一个delay的方法,返回一个promise,会等待在这里。等待到promise变成完成态
    yield fork(delay,1000);//yield 调用一个delay的方法,返回一个promise,直接执行不会堵塞
    
    yield put({type:'counter/minus'});//put派发一个动作,相当于dispatch;注意: put 也是阻塞 effect
    let state = yield select(state=>state.counter);  //获取store的数据相当于 store.getState()
    
    let {push} = routerRedux;//dva封装的router-redux,dva的核心库没有这个方法。
    yield put(push(action.to));
}

//effects里面调取公共的方法  例:
 effects: {
    * a(action, person) {
        console.log(8888)
    },
    * b(action, person) {
        yield put({//就是dispatch
            type: 'a',
        })
    }
}

//dva请求多个接口
//方法一 :利用Promise.all
const [ary1,ary2] = yield Promise.all([queryWxapps(scenicid()['id']), queryWxapps(scenicid()['id']), queryWxapps(scenicid()['id'])])
console.log(ary1);//[{},{}]

//方法二 :利用axios.all()
axios.all([queryWxapps(scenicid()['id']), queryWxapps(scenicid()['id']), queryWxapps(scenicid()['id']).then(axios.spread((user, aside) => {
  console.log(user);
  console.log(aside);
}));
//方法三 :sage提供的将会同步执行
const [a,b,c] = yield [
    call(groupbuysList, shopid()['id'], action.params),
    call(groupbuysList, shopid()['id'], action.params),
    call(groupbuysList, shopid()['id'], action.params)
];
//方法四 :sage提供的yield all
 const [a,b,c]  = yield all([
    call(groupbuysList, shopid()['id'], action.params),
    call(groupbuysList, shopid()['id'], action.params),
    call(groupbuysList, shopid()['id'], action.params),
]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49