React基础

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作 “组件”

在使用react之前我们需要确保我们计算机中安装有Node.js运行环境。随后打开命令行,我们可以用npx create-react-app <应用名>来快速创建一个React应用。

在新建项目中可以看到src/index.js文件中最重要的是ReactDOM.render()函数,整个页面的渲染都是从这里开始的。

ReactDOM.render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
    document.getElementById('root')
);

第一个参数是我们要渲染的所有元素,可以看到这里用的是HTML语法。第二个参数代表将渲染的结果插入到页面中的哪一个容器中,这里对应public/index.html中id为root的元素:<div id="root"></div>
第一个参数其实是React对JS的一种语法扩展,被称为JSX。它允许将HTML标签和JS代码混合使用,所以我们不再需要分开定义HTML和JS文件,编写和阅读代码会方便很多。可以尝试去修改这个标签:

ReactDOM.render(
    <h1>hello,React!!!</h1>
    document.getElementById('root')
);

浏览器中的内容也会自动刷新

image.png

当然了,这里面的<App />不是一个标准的标签,按住ctrl点它会发现,它会跳到另一个函数的定义。因为React中的组件都可以被简化成一个函数的定义 函数的输出是一段HTML标签,也就是组件最终渲染的结果。我们可以通过组合其他组件或者HTML标签来创建更为复杂的组件

举个例子:根据数据来动态地生成用户界面,数据通常是通过API从后端获取,这里为了简单,在文件开头定义了一个返回假数据的函数fetchTodos()

//src/index.js
function APP(){
  const todos=fetchTodos();//调用函数获取数据
  //通过map遍历todos中的每一条数据
  return (
    <>
      <ul>
        {todos.map((todo) => (
          <li>
            <input type="defaultChecked={todo.completed}" />
            <label>{todo.title}</label>
          </li>
        ))}
      </ul>
    </>;
  )
}

ReactDOM.render(<App/>, document.getElementById("root"));

注意:如果我们想在HTML标签中嵌入JS的表达式,需要将它们嵌套在花括号{}中.
这里生成的每一个列表元素由复选框input和一个标签label组成,复选框是否选中取决于数据中的compelted属性。

因为很多常见的框架在React中都有很好的集成。所以可以直接用react-bootstrap来对页面做美化。 直接按照文档中的步骤来安装这个包

npm install react-bootstrap bootstrap@5.1.3

然后在文件中导入要用的组件和样式表(VSCode会在输入组件的时候自动导入缺少的包),替换之前的HTML标签:

function APP(){
  const todos=fetchTodos();
  
  return (
    <>
      <Container>
        {todos.map((todo) => (
          <InputGroup key={todo.id}>
            <InputGroup.Checkbox checked={todo.completed} />
            <FormControl
              value={todo.title}
              style={
                textDecoration:todo.completed ? "line-through 4px" : "none",
              }
            />
          </InputGroup>
        ))}
      </Container>
    </>;
  )
}
//加入了删除线效果

当组件变得复杂以后,我们可以考虑将可重用的代码拆分成一个个独立的组件,也就是模块化,比如列表元素可以被抽离成一个单独的组件TodoItem。而列表中的文字和复选框状态可以用类似于HTML标签属性的语法并作为函数参数props传递进来

function TodoItem(props){ // props 属性 是只读的,我们不可以在函数内部修改它 
  return (
    <InputGroup key={props.id}>
      <InputGroup.Checkbox checked={props.completed} />
      <FormControl
        value={props.title}
        style={
          textDecoration:props.completed ? "line-through 4px" : "none",
        }
      />
    </InputGroup>
  )
}
function App(){
  return (
    <>
      <Container>
        {todos.map((todo) => (
          <TodoItem
            key={todo.id}
            title={tofo.title}
            completed={todo.completed}
          />
        ))}
      </Container>
    </>;
  )
}

为了实现数据的动态更新,需要用到states状态,在React中定义一个状态,需要用到useState()方法。它会返回两个值,第一个是存储当前状态变量的,第二个是存储用来修改状态变量的方法,最后的参数代表状态的初始值:

const [todos,setTodos]=useState(fetchTodos());

要修改一个状态是不能直接对这里的todos变量赋值的,必须调用setTodos方法

function TodoItem(props){ // props 属性 是只读的,我们不可以在函数内部修改它 
  return (
    <InputGroup key={props.id}>
      <InputGroup.Checkbox
        checked={props.completed} 
        onChange={props.onToggle}
      />
      <FormControl
        value={props.title}
        style={
          textDecoration:props.completed ? "line-through 4px" : "none",
        }
      />
      <Button variant="outline-danger" onClick={props.onDelete}>
        <Trash />
      </Button>
    </InputGroup>
  )
}
//按钮事件 -> 调用setTodos()更新状态 -> 界面刷新
function App(){
  return (
    <>
      <Container>
        {todos.map((todo) => (
          <TodoItem
            key={todo.id}
            title={tofo.title}
            completed={todo.completed}
            onDelete={() => {
              setTodos(todos.filter((x) => x.id !== todo.id));
            }}
            onToggle={() => {
              setTodos(
                todos.map((x) => 
                  x.id === todo.id ? ...x, compelted : !x.completed} : x
              );
            }}
          />
        ))}
      </Container>
    </>;
  )
}

props及state

组件接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

组件

  • 函数式组件
  • Class类组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

渲染组件

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

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

组件的组合与拆分

// 页面内多次引用
<div>
  <Welcome name="Sara" />
  <Welcome name="Cahal" />
  <Welcome name="Edite" />
</div>

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

// 拆分后为
function UserInfo(){
  return (
    <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
    </div>
  )
}

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

state

  1. setState 构造函数是唯一可以给state赋值的地方
this.setState({comment: 'Hello'});
  1. state更新可能是异步的
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
// Correct
this.setState(function(state, props) {
  return {
    counter: state.counter + props.increment
  };
});
  1. state更新会合并
constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

componentDidMount() {
  fetchPosts().then(response => {
    this.setState({
      posts: response.posts
    });
  });

  fetchComments().then(response => {
    this.setState({
      comments: response.comments
    });
  });
}
  1. 单向数据流 state 只在当前的组件里生效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是不一样的; 例如Clock中计时器都是独立的

合成事件

class App extends Component { 
        state = { val: 0 }
        
        increment = () => {
                this.setState({ val: this.state.val + 1 })
                console.log(this.state.val) // 输出的是更新前的val --> 0
        }
        
        render() {
                return (
                        <div onClick={this.increment}>
                                {Counter is: ${this.state.val}}
                        </div>
                )
        }
}

生命周期

class App extends Component {
        state = { val: 0 }
        
        componentDidMount() {
                this.setState({ val: this.state.val + 1 })
                console.log(this.state.val) // 输出的还是更新前的值 --> 0
        }
        render() {
                return (
                        <div>
                                {Counter is: ${this.state.val}}
                        </div>
                )
        }
}

原生事件

class App extends Component { 
        state = { val: 0 }
        
        changeValue = () => {
                this.setState({ val: this.state.val + 1 })
                console.log(this.state.val) // 输出的是更新后的值 --> 1
        }
        
        componentDidMount() {
                document.body.addEventListener('click', this.changeValue, false)
        }
        
        render() {
                return (
                        <div>
                                {Counter is: ${this.state.val}}
                        </div>
                )
        }
}

setTimeout

class App extends Component {
        state = { val: 0 }
        
        componentDidMount() {
                setTimeout(_ => {
                        this.setState({ val: this.state.val + 1 })
                        console.log(this.state.val) // 输出更新后的值 --> 1
                }, 0)
        }
        
        render() {
                return (
                        <div>
                                {Counter is: ${this.state.val}}
                        </div>
                )
        }
}

批处理

class App extends Component {
        state = { val: 0 }
        
        batchUpdates = () => {
                this.setState({ val: this.state.val + 1 })
                this.setState({ val: this.state.val + 1 })
                this.setState({ val: this.state.val + 1 })
        }
        
        render() {
                return (
                        <div onClick={this.batchUpdates}>
                                {Counter is ${this.state.val}} // 1
                        </div>
                )
        }
}
  1. setState 只在合成事件和生命周期中是“异步”的,在原生事件和 setTimeout 中都是同步的;
  2. setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的, 只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”, 当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

生命周期

render

是class组件必需的方法,获取最新的 props 和 state, 在不修改组件 state 的情况下,每次调用时都返回相同的结果

constructor

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

  • 通过给 this.state 赋值对象来初始化内部 state。
  • 为事件处理函数绑定实例
constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

不要调用 setState() 并且避免将 props 的值复制给 state

this.state = { color: props.color }; // wrong

componentWillUnmount

componentWillUnmount() 会在组件卸载及销毁之前直接调用。例如,清除 timer,取消网络请求;

componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染;

事件处理

语法格式

  1. 无需调用addEventListener进行事件监听,也无需考虑兼容性,React已经封装好了一些的事件类型属性;
  2. 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串;
  3. 不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault;
// DOM
<button onclick="activateLasers()">
  Activate Lasers
</button>

// React
<button onClick={activateLasers}>
  Activate Lasers
</button>

// JS
<form onsubmit="console.log('You clicked submit.'); return false">
  <button type="submit">Submit</button>
</form> 
function Form() {
  function handleSubmit(e) {
    e.preventDefault(); 
    console.log('You clicked submit.');
  }
  
  return ( 
    <form onSubmit={handleSubmit}> 
      <button type="submit">Submit</button> 
    </form> 
  ); 
}

接收参数

  1. 事件对象 e 会被作为第二个参数传递;
  2. 通过箭头函数的方式,事件对象必须显式的进行传递;
  3. 通过 Function.prototype.bind 的方式,事件对象以及更多的参数将会被隐式的进行传递;
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

条件渲染

if else 渲染

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);  //登录
    this.handleLogoutClick = this.handleLogoutClick.bind(this);  //登出
    this.state = {isLoggedIn: false};  //当前的登录状态
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }
  //渲染
  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

与运算符 &&

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
  <Mailbox unreadMessages={messages} />,
  document.getElementById('root')
);

// 返回false的表达式,会跳过元素,但会返回该表达式
render() {
  const count = 0;
  return (
    <div>
      { count && <h1>Messages: {count}</h1>}
    </div>
  );
}

三元运算符

render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

如何阻止组件渲染

function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}

class Page extends React.Component {
  constructor(props) {
    super(props);
    this.state = {showWarning: true};
    this.handleToggleClick = this.handleToggleClick.bind(this);
  }

  handleToggleClick() {
    this.setState(state => ({
      showWarning: !state.showWarning
    }));
  }

  render() {
    return (
      <div>
        <WarningBanner warn={this.state.showWarning} />
        <button onClick={this.handleToggleClick}>
          {this.state.showWarning ? 'Hide' : 'Show'}
        </button>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);

列表

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);
// 若没有key,会warning a key should be provided for list items
// key可以帮助react diff,最好不用index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使用索引用作为列表项目的 key 值;

key注意点

key要保留在map的遍历元素上

// demo1
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')
);

// demo2
function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

// demo3
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}
全部评论

相关推荐

陆续:不可思议 竟然没那就话 那就我来吧 :你是我在牛客见到的最美的女孩
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务