# 错误提示及调整方法

返回:react

[[toc]

# Invariant Violation: Could not find “store” in either the context or props of “Connect(SportsDatabase)”

reason

It's pretty simple. You're trying to test the wrapper component generated by calling connect()(MyPlainComponent).
That wrapper component expects to have access to a Redux store.
Normally that store is available as context.store, because at the top of your component hierarchy you'd have a <Provider store={myStore} />.
However, you're rendering your connected component by itself, with no store, so it's throwing an error.

很简单您正在尝试测试通过调用connect()(MyPlainComponent)生成的包装器组件。
该包装器组件希望可以访问Redux存储。
通常,该存储可以作为context.store使用,因为在组件层次结构的顶部,您将具有一个<Provider store = {myStore} />。 但是,您将自己渲染连接的组件,没有存储,因此会引发错误。

You've got a few options:

  • Create a store and render a <Provider> around your connected component
    • 创建一个store并在所连接的组件周围渲染一个<Provider>
  • Create a store and directly pass it in as <MyConnectedComponent store={store} /> , as the connected component will also accept "store" as a prop
    • 创建一个store并将其直接作为<MyConnectedComponent store = {store} />传入,因为连接的组件也将接受“ store”作为属性
  • Don't bother testing the connected component. Export the "plain", unconnected version, and test that instead. If you test your plain component and your mapStateToProps function, you can safely assume the connected version will work correctly.
    • 不要费心测试连接的组件。导出“普通”未连接版本,然后进行测试。如果您测试普通组件和mapStateToProps函数,则可以安全地假定连接的版本将正常工作。

# 编写 React 组件时常见的 5 个错误

# 在不需要重渲染时使用 useState

使用 useState hook,你现在还可以在函数组件中定义状态,这种方法可以真正简洁地在 React 中处理状态。但正如以下示例所示,它也可能被滥用。

// 因为我们从未在渲染部分中使用这个状态,结果每次设置计数器时都会有不必要的重渲染,这可能会影响性能或产生意外的副作用。
function ClickButton(props) {
  const [count, setCount] = useState(0);
  const onClickCount = () => {
    setCount((c) => c + 1);
  };
  const onClickRequest = () => {
    apiCall(count);
  };
  return (
    <div>
      <button onClick={onClickCount}>Counter</button>
      <button onClick={onClickRequest}>Submit</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

希望该变量在渲染之间保持其值,但又不强制重新渲染,则可以使用 useRef hook。它将保留值,但不强制重新渲染组件。

function ClickButton(props) {
  const count = useRef(0);
  const onClickCount = () => {
    count.current++;
  };
  const onClickRequest = () => {
    apiCall(count.current);
  };
  return (
    <div>
      <button onClick={onClickCount}>Counter</button>
      <button onClick={onClickRequest}>Submit</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 使用 router.push 代替链接

假设你要编写一个按钮,单击该按钮应将用户重定向到另一个页面。由于它是一个 SPA,因此这个动作是客户端路由机制。于是你需要某种库来执行此动作。在 React 中最流行的是 react-router,下面的示例就会使用它

function ClickButton(props) {
  const history = useHistory();
  const onClick = () => {
    history.push('/next-page');
  };
  return <button onClick={onClick}>Go to next page</button>;
}
1
2
3
4
5
6
7

WARNING

就算这段代码对于大多数用户来说都可以正常工作,但这里也有严重的可访问性问题。这个按钮根本不会被标记为链接到另一个页面,于是屏幕阅读器几乎无法识别它。而且你能在新标签页或窗口中打开它吗?很可能做不到。

只要指向其他页面的链接带有某种用户交互,就要尽量用 < Link> 组件或常规的 < a> 标签处理。

function ClickButton(props) {
  return (
    <Link to="/next-page">
      <span>Go to next page</span>
    </Link>
  );
}
1
2
3
4
5
6
7

# 通过 useEffect 处理动作

想象一下有一个组件,其获取一个项目列表并将其渲染给 dom。另外,如果请求成功,我们将调用“onSuccess”函数,该函数作为一个 prop 传递给这个组件。

// 是不是傻子?
function DataList({ onSuccess }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);
  const fetchData = useCallback(() => {
    setLoading(true);
    callApi()
      .then((res) => setData(res))
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  }, []);
  useEffect(() => {
    fetchData();
  }, [fetchData]);
  useEffect(() => {
    if (!loading && !error && data) {
      onSuccess();
    }
  }, [loading, error, data, onSuccess]);
  return <div>Data: {data}</div>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

解决方案

function DataList({ onSuccess }) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);
  const fetchData = useCallback(() => {
    setLoading(true);
    callApi()
      .then((fetchedData) => {
        setData(fetchedData);
        onSuccess();
      })
      .catch((err) => setError(err))
      .finally(() => setLoading(false));
  }, [onSuccess]);
  useEffect(() => {
    fetchData();
  }, [fetchData]);
  return <div>{data}</div>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 单一责任组件

# 单一责任的 useEffects