美文网首页React
React入门系列一

React入门系列一

作者: Eastboat | 来源:发表于2019-10-08 13:00 被阅读0次

    近期开始入门react,对于我这样的前端码畜来讲
    Q: 入门最好的方式是什么?(黑人问号脸)
    A: 当然是看文档呀!!!
    ps:我已经断断续续看了一星期的官方文档,总体来讲,我觉得文档写的很详细,详细的程度以致于我觉得有很多啰嗦的话(哈哈),所以一边敲案例,一边做自己的总结性的归纳。
    附:大佬的对话,说的还是有道理的

    image.png

    jsx

    1.在 JSX 当中的表达式要包含在大括号里

    2.书写 JSX 的时候一般都会带上换行和缩进,这样可以增强代码的可读性

    3.推荐在 JSX 代码的外面扩上一个小括号,这样可以防止 分号自动插入的 bug

    4.JSX 标签是闭合式的,那么你需要在结尾处用 />, 就好像 XML/HTML 一样

    //定义对象数据,给下面面类似"纯函数的组件"使用
    const user = {
      firstName: 'Harper',
      lastName: 'Perez'
    };
    //jsx中调用此函数表达式
    function formatName(user) {
      return user.firstName + ' ' + user.lastName;
    };
    // jsx换行缩进,增强可读性
    const element = (
      <h1>
        Hello, {formatName(user)}!
      </h1>  {/* JSX标签是闭合式的 */}
    );
    // 渲染元素
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

    5.Babel 转译器会把 JSX 转换成一个名为 React.createElement() 的方法调用。

    const element = <h1 className="greeting">Hello, world!</h1>;
    //两种代码的作用是完全相同
    const element = React.createElement(
      "h1",
      { className: "greeting" },
      "Hello, world!"
    );
    

    6.JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 camelCase 小驼峰命名 来定义属性的名称,而不是使用 HTML 的属性名称

    //class 变成了 className,而 tabindex 则对应着 tabIndex
    function App(props) {
      return (
        <div className="Comment" tabIndex="1">
          {/*
                             do something   
                        */}
        </div>
      );
    }
    

    元素渲染

    1. 元素是构成 React 应用的最小单位。
    2. 元素事实上只是构成组件的一个部分
    3. 在实际生产开发中,大多数 React 应用只会调用一次 ReactDOM.render()
    4. React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分。

    组件 & Props

    1. 组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的 React 元素。

    2. 函数组件

      function Welcome(props) {
        return <h1>Hello, {props.name}</h1>;
      }
      
    3. 类组件

      class Welcome extends React.Component {
        render() {
          return <h1>Hello, {this.props.name}</h1>;
        }
      }
      
    4. 遇到的 React 元素都只是 DOM 标签,也可以是用户自定义的组件,当 React 遇到的元素是用户自定义的组件,它会将 JSX 属性作为单个对象传递给该组件,这个对象称之为“props”。

      class AppComponent extends React.Component {
        render() {
          return <h1>This is class define component: {this.props.name}</h1>;
        }
      }
      //组件名称必须以大写字母开头。<div /> 表示一个DOM标签,但 <App /> 表示一个组件
      ReactDOM.render(
        <AppComponent name="ReactApp" />,
        document.getElementById("root")
      );
      
    5. 通常,一个新的 React 应用程序的顶部是一个 App 组件。但是,如果要将 React 集成到现有应用程序中,则可以从下而上使用像 Button 这样的小组件作为开始,并逐渐运用到视图层的顶部。

      
        function Welcome(props){
              return <h1>Hello, {props.name}</h1>
          }
          class App extends React.Component {
              render() {
                  return (
              {
                  /*
                  组件的返回值只能有一个根元素。这也是我们要用一个<div>来包裹所有<Welcome />元素的原因。
                  */
               }
                      <div>
                          <h1>This is class define component: {this.props.name}</h1>
                          <Welcome name="1111" />
                          <Welcome name="2222" />
                          <Welcome name="3333" />
                      </div>
                  )
              }
          }
          ReactDOM.render(<App name="ReactApp" />,document.getElementById("root"))
      

    State & 生命周期

    React 是非常灵活的,但它也有一个严格的规则:所有的 React 组件必须像纯函数那样使用它们的 props。何为纯函数? 字面意思就是给你什么返回什么,不关注函数内部的东西,引用函数式编程里面的一句话:所有纯函数必须遵守引用透明性

    状态与属性十分相似,但是状态是私有的,完全受控于当前组件。

    封装时钟(函数组件)

    //封装时钟, 理想情况下,我们写一次 Clock 然后它能更新自身,为了实现这个需求,我们需要为Clock组件添加状态
    function Clock(props) {
      return (
        <div>
          <h1>Hello, world!</h1>
          <h2>It is {props.date.toLocaleTimeString()}.</h2>
        </div>
      );
    }
    function tick() {
      ReactDOM.render(<Clock date={new Date()} />, document.getElementById("root"));
    }
    setInterval(tick, 1000);
    

    类组件实现

    Clock 现在被定义为一个类而不只是一个函数,使用类就允许我们使用其它特性,例如局部状态、生命周期钩子

    //将函数转换为类
    class Clock extends React.Component {
      render() {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
          </div>
        );
      }
    }
    
    //为一个类添加局部状态State
    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          date: new Date(),
          name: this.props.name //将props存入state
        };
      }
      render() {
        return (
          <div>
            <h1>hello,React!</h1>
            <h2>this is Clock component:{this.state.date.toLocaleTimeString()}</h2>
            <h3>{this.props.name}</h3>
            <h3>{this.state.name}</h3>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock name="这是我自定义的props:name" />,
      document.getElementById("root")
    );
    

    类组件中加入 React 的生命周期方法

    1. 在具有许多组件的应用程序中,在销毁时释放组件所占用的资源非常重要。
    2. 第一次加载到 DOM 中的时候叫作挂载,vue 中采用$mount("#app")
    3. OM 被移除的时候叫作卸载

    我们可以在组件类上声明特殊的方法,当组件挂载或卸载时,来运行一些代码:

    class Clock extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          date: new Date(),
          name: this.props.name
        };
      }
      //生命周期钩子
      componentDidMount() {
        this.timerID = setInterval(() => this.tick(), 1000);
      }
      //生命周期钩子
      componentWillUnmount() {
        clearInterval(this.timerID);
      }
      tick() {
        //调用 setState() 来调度UI更新
        this.setState({
          date: new Date()
        });
      }
      render() {
        return (
          <div>
            <h1>hello,React!</h1>
            <h2>this is Clock component:{this.state.date.toLocaleTimeString()}</h2>
            <h3>{this.props.name}</h3>
            <h3>{this.state.name}</h3>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Clock name="这是我自定义的props:name" />,
      document.getElementById("root")
    );
    

    State 正确使用

    1.不要直接更新状态

    应当使用 setState(),构造函数是唯一能够初始化 this.state 的地方。

    2.状态更新可能是异步的

    React 可以将多个 setState() 调用合并成一个调用来提高性能。this.props 和 this.state 可能是异步更新的,你不应该依靠它们的值来计算下一个状态。请使用第二种形式的 setState() 来接受一个函数而不是一个对象。 该函数将接收先前的状态作为第一个参数,将此次更新被应用时的 props 做为第二个参数:

    // es6大括号解释为块代码,如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则报错
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment
    }));
    
    this.setState(function(prevState, props) {
      return {
        counter: prevState.counter + props.increment
      };
    });
    
    3.状态更新合并

    当你调用 setState() 时,React 将你提供的对象合并到当前状态。

    class CommentApp extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          user: "eastboat1",
          comments: [
            { id: 1, content: "这是评论1" },
            { id: 2, content: "这是评论2" },
            { id: 3, content: "这是评论3" }
          ]
        };
      }
      //生命周期钩子
      componentDidMount() {
        //模拟异步数据修改state ,独立更新对应的state
        setTimeout(() => {
          this.setState({
            user: "eastboat2"
          });
        }, 3000);
      }
      componentWillUnmount() {}
      render() {
        return (
          <div>
            <h2>this is my component:CommentApp</h2>
            <h2>{this.state.user}</h2>
            <h2>{this.props.UserName}</h2>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <CommentApp UserName="这是我自定义的props:UserName" />,
      document.getElementById("root")
    );
    

    数据流向(单向流)

    组件可以选择将其状态作为属性传递给其子组件:这通常被称为自顶向下或单向数据流。 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。

    事件处理

    通常建议在构造函数中绑定或使用属性初始化器语法来避免这类

    1. React 事件绑定属性的命名采用驼峰式写法,而不是小写
    2. 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM 元素的写法)
    //原生html中
    <button onclick="handleChange">点击按钮</button>
    
    //react
    <button onClick={handleChange}>点击按钮</button>
    
    1. 在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault
    //类组件中 不能使用返回 false 的方式阻止默认行为
    class App extends React.Component {
      render() {
        function handleClick(e) {
          e.preventDefault();
          console.log("The link was clicked.");
        }
        return (
          <a href="www.baidu.com" onClick={handleClick}>
            a标签
          </a>
        );
      }
    }
    
    类的方法
    1. 使用 React 的时候通常你不需要使用 addEventListener 为一个已创建的 DOM 元素添加监听器。你仅仅需要在这个元素初始渲染的时候提供一个监听器。
    class Toggle extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          isToggleOn: true
        };
        this.handleClick = this.handleClick.bind(this);
      }
      //类方法,但是类的方法默认是不会绑定 this 的,需要上面的bind
      handleClick() {
        this.setState(prevState => ({
          isToggleOn: !prevState.isToggleOn
        }));
      }
    
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? "ON" : "OFF"}
          </button>
        );
      }
    }
    
    属性初始化器语

    这种语法确保this绑定在 handleClick 中

    class LoggingButton extends React.Component {
      handleClick = () => {
        console.log("this is:", this);
      };
      render() {
        return <button onClick={this.handleClick}>Click me</button>;
      }
    }
    
    回调函数中使用箭头函数
    class LoggingButton extends React.Component {
      handleClick() {
        console.log("this is:", this);
      }
    
      render() {
        return <button onClick={e => this.handleClick(e)}>Click me</button>;
      }
    }
    
    向事件处理程序传递参数
    //二者等价
    //箭头函数的方式,事件对象必须显式的进行传递
    <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
    
    //bind 的方式,事件对象以及更多的参数将会被隐式的进行传递
    <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
    

    通过 bind 方式向监听函数传参,在类组件中定义的监听函数,事件对象 e 要排在所传递参数的后面

    class App extends React.Component {
      constructor() {
        super();
        this.state = { name: "Hello world!" };
      }
      preventPop(name, e) {
        //事件对象e要放在最后
        e.preventDefault();
        alert(name);
      }
      render() {
        return (
          <div>
            <p>hello</p>
            <a
              href="https://reactjs.org"
              onClick={this.preventPop.bind(this, this.state.name)}
            >
              Click
            </a>
          </div>
        );
      }
    }
    

    条件渲染

    if 条件判断

    使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态

    //组件一
    function ComponentOne() {
      return <h1>this is component one</h1>;
    }
    //组件二
    function ComponentTwo() {
      return <h1>this is component two</h1>;
    }
    //容器组件
    function App(props) {
      const status = props.isLoginStatus;
      if (status) {
        return <ComponentOne />;
      } else {
        return <ComponentTwo />;
      }
    }
    
    使用元素变量存储元素
    function LoginButton(props) {
      return <button onClick={props.onClick}>登录</button>;
    }
    
    function LogoutButton(props) {
      return <button onClick={props.onClick}>登出</button>;
    }
    class LoginControl extends React.Component {
      constructor(props) {
        super(props);
        this.handleLoginClick = this.handleLoginClick.bind(this);
        this.handleLogoutClick = this.handleLoginClick.bind(this);
        this.state = {
          isLoginStatus: false
        };
      }
      handleLoginClick() {
        //登录
        this.setState({
          isLoginStatus: true
        });
      }
      handleLogoutClick() {
        //登出
        this.setState({
          isLoginStatus: false
        });
      }
    
      render() {
        const status = this.state.isLoginStatus;
        let button; //创建变量存储组件元素
        if (status) {
          button = <LogoutButton onClick={this.handleLogoutClick} />;
        } else {
          button = <LoginButton onClick={this.handleLoginClick} />;
        }
        return <div>{button}</div>;
      }
    }
    
    与运算符 &&

    在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

    const arr = ["a", "b", "c"];
    function App(props) {
      const list = props.list;
      return <div>{list.length > 0 && <h1>这是list列表</h1>}</div>;
    }
    ReactDOM.render(<App list={arr} />, document.getElementById("root"));
    
    三目运算符
    //文本渲染使用
    return (
      <div>
        The user is <b>{isLoggedIn ? "currently" : "not"}</b> logged in.
      </div>
    );
    //组件渲染使用
    return (
      <div>
        {isLoggedIn ? (
          <LogoutButton onClick={this.handleLogoutClick} />
        ) : (
          <LoginButton onClick={this.handleLoginClick} />
        )}
      </div>
    );
    
    阻止组件渲染
    1. 可以让 render 方法直接返回 null,而不进行任何渲染
    2. 在组件的 render 方法中返回 null 并不会影响组件的生命周期
    function WarningBanner(props) {
      if (!props.warn) {
        return null;
      }
    
      return <div className="warning">Warning!</div>;
    }
    

    列表 && key

    map() 函数
    // map() 不会对空数组进行检测
    // map() 不会改变原始数组。
    const numbers = [1, 2, 3, 4, 5];
    const doubled = numbers.map(number => number * 2);
    // [2, 4, 6, 8, 10]
    
    key 用作确定的标识
    1. 一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据 id 来作为元素的 key
    2. 如果列表项目的顺序可能会变化,我们不建议使用索引来用作 key 值,因为这样做会导致性能变差,还可能引起组件状态的问题
    class App extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        const list = this.props.listitem;
        const ListItem = list.map((val, index, arr) => {
          return <li key={index}>{val}</li>;
        });
        return (
          <div>
            <ul>{ListItem}</ul>
          </div>
        );
      }
    }
    const arr = ["a", "b", "c", "d", "e"];
    ReactDOM.render(<App listitem={arr} />, document.getElementById("root"));
    
    1. 元素的 key 只有放在就近的数组上下文中才有意义。
    function ListItem(props) {
      // 正确!这里不需要指定 key:
      return <li>{props.value}</li>;
    }
    
    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map(number => (
        // 正确!key 应该在数组的上下文中被指定
        <ListItem key={number.toString()} value={number} />
      ));
      return <ul>{listItems}</ul>;
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById("root")
    );
    
    1. key 只是在兄弟节点之间必须唯一

      数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值:

    JSX 中嵌入 map()
    function ListItem(props) {
      return <li>{props.value}</li>;
    }
    class App extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        const list = this.props.listitem;
        return (
          <div>
            <ul>
              {/*此处是jsx中的写法*/
              list.map((val, index, arr) => (
                <ListItem value={val} key={index} />
              ))}
            </ul>
          </div>
        );
      }
    }
    const arr = ["a", "b", "c", "d", "e"];
    ReactDOM.render(<App listitem={arr} />, document.getElementById("root"));
    

    表单

    在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state

    受控组件

    表单元素通常自己维护 state,并根据用户输入进行更新,而可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新,两者结合起来,使得 React 的 state 成为“唯一数据源,渲染表单的 React 组件还控制着用户输入过程中表单发生的操作,这种表单输入元素就叫做“受控组件”。

    class MyFormComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = { value: "初始化state内容" };
        this.handleChange = this.handleChange.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
      }
      handleChange(e) {
        this.setState({
          value: e.target.value
        });
      }
      handleSubmit(e) {
        e.preventDefault();
        console.log(e.target);
        console.log(this.state.value);
      }
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <input
              type="text"
              value={this.state.value}
              onChange={this.handleChange}
            />
            <input type="submit" value="提交" />
          </form>
        );
      }
    }
    
    ReactDOM.render(<MyFormComponent />, document.getElementById("root"));
    
    textarea 标签

    在 HTML 中, textarea 元素通过其子元素定义其文本,而在 React 中,textarea 使用 value 属性代替,和上面的 input 一样绑定 value 属性

    select 标签

    React 并不会使用 selected 属性,而是在根 select 标签上使用 value 属性

    //可以将数组传递到 value 属性中,以支持在 select 标签中选择多个选项
    <select multiple={true} value={['B', 'C']}>
    
    处理多个输入

    当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作

    状态提升

    多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去

    1. 在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”
    2. 应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。

    组合 VS 继承

    推荐使用组合而非继承来实现组件间的代码重用

    在 React 中没有“槽”这一概念的限制,你可以将任何东西作为 props 进行传递

    1. 使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中
      function App(props) {
        return <div>{props.children} //渲染子组件</div>;
      }
      //组件中的子组件内容
      function ParentComponent() {
        return (
          <App>
            <p>这是子组件内容</p>
            <p>这是子组件内容</p>
            <p>这是子组件内容</p>
          </App>
        );
      }
      
    2. 少数情况下,你可能需要在一个组件中预留出几个“洞”。这种情况下,我们可以不使用 children,而是自行约定:将所需内容传入 props,并使用相应的 prop。
       function App(props) {
         return (
           <div>
               <div className='CommOne'>
                   { props.left }
               </div>
               <div className='CommTwo'>
                   { props.right }
               </div>
           </div>
         );
       }
       //  如  <leftComponent />  <rightComponent /> React 元素本质就是对象(object)
       function ParentComponent() {
         return (
           <App
               left={
                 <leftComponent />
               }
               right={
                 <rightComponent />
               }
           />
           );
       }
       ```
      **Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数**
      

    如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们

    React 哲学

    我们平时在公司中,都会和 UI 小姐姐(小哥哥)对接,做项目时首先拿到项目的 UI 设计稿,这时候我们就需要站在组件的维度去思考这个 UI 设计稿。

    1.划分为组件层级

    根据 UI 设计稿划分为组件层级,以合适的名称命名,UI(组件结构)和数据模型都会倾向于遵守相同的信息结构,所以 UI 和 JSON 数据模型一一对应

    Q:如何确定应该将哪些部分划分到一个组件中呢?
    A:可以将组件当作一种函数或者是对象来考虑,
      根据单一功能原则来判定组件的范围。
      也就是说,一个组件原则上只能负责一个功能。
    
    2.编写静态的组件
    1. 先用已有的数据模型渲染一个不包含交互功能的 UI
    2. 最好将渲染 UI 和添加交互这两个过程分开,因为往往要编写大量代码,而不需要考虑太多交互细节,而添加交互功能时则要考虑大量细节,不需要编写太多代码
    3. 当你的应用比较简单时,使用自上而下的方式更方便;对于较为大型的项目来说,自下而上地构建,并同时为低层组件编写测试是更加简单的方式。
    4. state 代表了随时间会产生变化的数据,应当仅在实现交互时使用。所以构建应用的静态版本时,你不会用到它
    3.确定所需的 state 的最小集合

    通过问自己以下三个问题,你可以逐个检查相应数据是否属于 state

    1.该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
    2.该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
    3.你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
    
    4.确定 state 放置的位置

    上面已经确定了应用所需的 state 的最小集合。接下来,我们需要确定哪个组件能够改变这些 state,或者说拥有这些 state。

    1.找到根据这个 state 进行渲染的所有组件。
    2.找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
    3.该共同所有者组件或者比它层级更高的组件应该拥有该 state。
    4.如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置。
    5.于高于共同所有者组件层级的位置。
    
    5.添加反向数据流( setState )

    React 通过一种比传统的双向绑定略微繁琐的方法来实现反向数据传递。尽管如此,但这种需要显式声明的方法更有助于人们理解程序的运作方式。
    我们通过 setState()函数让数据反向传递:处于较低层级的表单组件更新较高层级中的 state。

    至此 react 文档基本核心内容已看完,再也不用担心被 T 掉了

    相关文章

      网友评论

        本文标题:React入门系列一

        本文链接:https://www.haomeiwen.com/subject/oxrbpctx.html