React入门教程(9)Ajax与React的上下文

ajax请求

react的组件中,一般我们在 componentDidMount事件中做ajax请求,并获得数据后修改state。

请求后台获取数据可以用任何的ajax库,建议使用: 原生的fetch或者axios库。

例如新闻案例:

import React, { Component } from 'react'
import axios from 'axios';

class NewsList extends Component {
  constructor(opt) {
    super(opt);
    this.state = {
      newsList: []
    };
  }

  componentDidMount() {
    // 发送ajax请求到后台并获取数据
    axios
      .get('/db.json')
      .then(res => {
        // console.log(res.data.news);
        this.setState({newsList: res.data.news});
      });
  }

  delNews(id) {
    // 不模拟从后台ajax请求删除数据
    // 直接在当前数组中移除数据。
    if(window.confirm('您是否要真的删除吗?')) {
      this.setState(preState => {
        return {
          newsList: preState.newsList.filter( item => item.id !== id)
        }
      });
    }
  }

  render () {
    return (
      <div>
        <table className="table is-striped is-hoverable is-bordered is-fullwidth">
          <thead>
            <tr>
              <th>编号</th>
              <th>新闻标题</th>
              <th>编辑</th>
            </tr>
          </thead>
          <tbody>
          {
            this.state.newsList.map((item, index) => {
              return (
                <tr key={index}>
                  <td>{item.id}</td>
                  <td>{item.title}</td>
                  <td>
                    <button
                      className="button is-primary" 
                      onClick={ this.delNews.bind(this, item.id) }
                    >
                      删除
                    </button>
                  </td>
                </tr>
              )
            })
          }
          </tbody>
        </table>
      </div>
    )
  }
}

export default NewsList;

React和DOM之间的属性区别

React实现了一套与浏览器无关的DOM系统,兼顾了性能和跨浏览器的兼容性。在React和Html之间有许多属性的行为是不同的。

checked

checked属性受类型为checkboxradio<input>组件的支持。你可以用它来设定是否组件是被选中的。这对于构建受控组件很有用。与之相对defaultChecked这是非受控组件的属性,用来设定对应组件首次装载时是否选中状态。

className

使用className属性指定一个CSS类。这个特性适用于所有的常规DOM节点和SVG元素,比如<div><a>和其它的元素。

如果你在React中使用Web组件(这是一种不常见的使用方式),请使用class属性来代替。

dangerouslySetInnerHTML

dangerouslySetInnerHTML是React提供的替换浏览器DOM中的innerHTML接口的一个函数。一般而言,使用JS代码设置HTML文档的内容是危险的,因为这样很容易把你的用户信息暴露给跨站脚本攻击.所以,你虽然可以直接在React中设置html的内容,但你要使用 dangerouslySetInnerHTML 并向该函数传递一个含有__html键的对象,用来提醒你自己这样做很危险。例如:

function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

htmlFor

因为for是在javascript中的一个保留字,React元素使用 htmlFor代替。

onChange

onChange事件的行为正如你所期望的:无论一个表单字段何时发生变化,这个事件都会被触发。我们故意不使用浏览器已有的默认行为,因为onChange在浏览器中的行为和名字不相称,React依靠这个事件实时处理用户输入。

selected

selected属性被<option>组件支持。你可以使用该属性设定组件是否被选择。这对构建受控组件很有用。

style

style属性接受一个JavaScript对象,其属性用小驼峰命名法命名,而不是接受CSS字符串。这和DOM中styleJavaScript 属性是一致性的,是更高效的,而且能够防止XSS的安全漏洞。例如:

const divStyle = {
  color: 'blue',
  backgroundImage: 'url(' + imgUrl + ')',
};

function HelloWorldComponent() {
  return <div style={divStyle}>Hello World!</div>;
}

注意样式不会自动补齐前缀。为了支持旧的浏览器,你需要手动提供相关的样式属性:

const divStyle = {
  WebkitTransition: 'all', // note the capital 'W' here
  msTransition: 'all' // 'ms' is the only lowercase vendor prefix
};

function ComponentWithTransition() {
  return <div style={divStyle}>This should work cross-browser</div>;
}

样式key使用小驼峰命名法是为了从JS中访问DOM节点的属性保持一致性(例如 node.style.backgroundImage)。供应商前缀除了ms,都应该以大写字母开头。这就是为什么WebkitTransition有一个大写字母W

React将自动添加一个"px"后缀到某些数字内联样式属性。如果你希望使用不同于"px"的其他单位,指定值为带渴望单位的字符串。例如:

// Result style: '10px'
<div style={{ height: 10 }}>
  Hello World!
</div>

// Result style: '10%'
<div style={{ height: '10%' }}>
  Hello World!
</div>

不是所有样式属性被转化为像素字符串,尽管如此。某些个保持无单位(例如 zoom, order, flex)。A complete list of 无单位属性 can be seen here.

value

value属性受到<input><textarea> 组件的支持。你可以使用它设置组件的值。这对构建受控组件非常有用。defaultValue属性对应的是非受控组件的属性,用来设置组件第一次装载时的值。

其他受支持的HTML属性

As of React 16, 任何标准的或自定义的 DOM属性都被充分支持。

React 总是提供一个以 JavaScript为中心的API给DOM。因为React组件对于自定义和DOM相关的属性都经常采用。React使用小驼峰约定,正如DOM API:

<div tabIndex="-1" />      // Just like node.tabIndex DOM API
<div className="Button" /> // Just like node.className DOM API
<input readOnly={true} />  // Just like node.readOnly DOM API

这些属性的工作类似于对应的HTML属性,除了上述文档的特例。

(Context)上下文

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

在一个典型的 React 应用中,数据是通过 props 属性由上向下(由父及子)的进行传递的,但这对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI主题),这是应用程序中许多组件都所需要的。 Context 提供了一种在组件之间共享此类值的方式,而不必通过组件树的每个层级显式地传递 props

简单说就是,当你不想在组件树中通过逐层传递props或者state的方式来传递数据时,可以使用Context来实现跨层级的组件数据传递。

  • 使用props或者state传递数据,数据自顶下流。
数据自顶下流
  • 使用Context,可以跨越组件进行数据传递
跨越组件进行数据传递

何时使用 Context

Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。例如,在下面的代码中,我们通过一个“theme”属性手动调整一个按钮组件的样式:

function ThemedButton(props) {
  return <Button theme={props.theme} />;
}

// 中间组件
function Toolbar(props) {
  // Toolbar 组件必须添加一个额外的 theme 属性
  // 然后传递它给 ThemedButton 组件
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

使用 context, 我可以避免通过中间元素传递 props:

// 创建一个 theme Context,  默认 theme 的值为 light
const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  // ThemedButton 组件从 context 接收 theme
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

// 中间组件
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

注意

不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。

React.createContext

const {Provider, Consumer} = React.createContext(defaultValue);

创建一对 { Provider, Consumer }。当 React 渲染 context 组件 Consumer 时,它将从组件树的上层中最接近的匹配的 Provider 读取当前的 context 值。

如果上层的组件树没有一个匹配的 Provider,而此时你需要渲染一个 Consumer 组件,那么你可以用到 defaultValue 。这有助于在不封装它们的情况下对组件进行测试。

Provider

<Provider value={/* some value */}>

React 组件允许 Consumers 订阅 context 的改变。

接收一个 value 属性传递给 Provider 的后代 Consumers。一个 Provider 可以联系到多个 Consumers。Providers 可以被嵌套以覆盖组件树内更深层次的值。

Consumer

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

一个可以订阅 context 变化的 React 组件。

接收一个 函数作为子节点. 函数接收当前 context 的值并返回一个 React 节点。传递给函数的 value 将等于组件树中上层 context 的最近的 Provider 的 value 属性。如果 context 没有 Provider ,那么 value 参数将等于被传递给 createContext()defaultValue

注意

关于此案例的更多信息, 请看 render props.

每当Provider的值发生改变时, 作为Provider后代的所有Consumers都会重新渲染。 从Provider到其后代的Consumers传播不受shouldComponentUpdate方法的约束,因此即使祖先组件退出更新时,后代Consumer也会被更新。

通过使用与Object.is相同的算法比较新值和旧值来确定变化。

注意

(这在传递对象作为 value 时会引发一些问题Caveats.)

动态 Context

一个更加复杂的例子:

import React from 'react';
import ReactDOM from 'react-dom';

const ThemeContext = React.createContext({
  background: 'red',
  color: 'white'
});

通过静态方法React.createContext()创建一个Context对象,这个Context对象包含两个组件,<Provider /><Consumer />

class App extends React.Component {
  render () {
    return (
      <ThemeContext.Provider value={{background: 'green', color: 'white'}}>
        <Header />
      </ThemeContext.Provider>
    );
  }
}

复制代码<Provider />的value相当于现在的getChildContext()

class Header extends React.Component {
  render () {
    return (
      <Title>Hello React Context API</Title>
    );
  }
}
class Title extends React.Component {
  render () {
    return (
      <ThemeContext.Consumer>
        {context => (
          <h1 style={{background: context.background, color: context.color}}>
            {this.props.children}
          </h1>
        )}
      </ThemeContext.Consumer>
    );
  }
}

复制代码<Consumer />children必须是一个函数,通过函数的参数获取<Provider />提供的Context

几个可以直接获取Context的地方

实际上,除了实例的context属性(this.context),React组件还有很多个地方可以直接访问父组件提供的Context。比如构造方法:

constructor(props, context)

比如生命周期:

componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)

对于面向函数的无状态组件,可以通过函数的参数直接访问组件的Context。

const StatelessComponent = (props, context) => (
  ......
)

作用于多个上下文

为了保持 context 快速进行二次渲染, React 需要使每一个 Consumer 在组件树中成为一个单独的节点。

// 主题上下文, 默认light
const ThemeContext = React.createContext('light');

// 登陆用户上下文
const UserContext = React.createContext();

// 一个依赖于两个上下文的中间组件
function Toolbar(props) {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App组件提供上下文的初始值
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Toolbar />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

如果两个或者多个上下文的值经常被一起使用,也许你需要考虑你自己渲染属性的组件提供给它们。

实例

import React, { Component } from 'react'

const LocalContext = React.createContext();

const { Provider, Consumer } = LocalContext; 

function Container(props) {
  return <Title />
}

function Title(props) {
  return (
    <div>
      <Consumer>
        { context => {
          return (
            <div>
              {context.name} - { context.age}
            </div>
          )
        }}
      </Consumer>
    </div>
  )
}

class DM extends Component {
  componentDidMount() {
    console.log(this.props);
  }

  render() {
    return (
      <div>
        <Consumer>
          {
            user => (<div>
              <p>{ user.name }</p>
              <p>{ user.age }</p>
            </div>
            )
          }
        </Consumer>
        ---{ this.props.name }----
      </div>
    )
  }

}

class ContextDemo extends Component {
  constructor (props, context) {
    super(props, context)
    this.state = {
      User: {
        age: 19,
        name: 'aicoder'
      }
    }
  }

  render () {
    return (
      <div>
        <Provider value={ this.state.User }>
          <Container></Container>
          <DM></DM>
        </Provider>
        <hr/>
        <input
          onClick={
            () => this.setState(preState => {
              return {User: { ...preState.User, age: preState.User.age + 1 }}
            })
          }
          className="button is-primary"
          value={ this.state.User.name }
          type="button"
          />
      </div>
    )
  }
}

export default ContextDemo

关注老马

AICODER 官网 React教程视频地址

参考

  1. 官网文档
  2. 老马React视频地址: https://ke.qq.com/course/379234?tuin=1eb4a0a4
  3. AICODER官网地址:https://www.aicoder.com/

推荐阅读更多精彩内容

  • It's a common pattern in React to wrap a component in an ...
    jplyue阅读 2,716评论 0 2
  • 说在前面 关于 react 的总结过去半年就一直碎碎念着要搞起来,各(wo)种(tai)原(lan)因(le)。心...
    陈嘻嘻啊阅读 6,253评论 7 41
  • 以下内容是我在学习和研究React时,对React的特性、重点和注意事项的提取、精练和总结,可以做为React特性...
    科研者阅读 7,105评论 2 21
  • 1、定义 函数式接口就是只定义一个抽象方法的接口。接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为...
    秋韵未央阅读 101评论 0 0
  • 邻居L,有段时间和父母之间闹的不可开交,原因是,L谈了一个外省的男朋友,父母嫌男方家太远,到时候L嫁过去,万一受到...
    孙大圣说商业阅读 505评论 2 13
  • 1.登录iOS 开发者中心 点击账户2.png3.第一个是添加团队成员 第二个是 发布&调试 证书 &描述文件 ...
    __Mach阅读 333评论 0 1