오류 솔루션 : Uncaught TypeError

Oct 22 2020

포착되지 않은 TypeError : document.getElementById (...)가 null입니다.

여러 페이지에 연결된 단일 JavaScript 파일이 있습니다. 아래 스 니펫은 단일 페이지에 사용되는 기능입니다. 위의 오류 페이지 중 하나에 때 사용자의 이동 및 표시 하지 않는 기능 아래를 사용을하고는 참조 document.getElementById('button01').addEventListener('click', newThing);하단에 줄을. button01이 페이지에 존재하지 않는 사실 때문에 오류가 발생한다고 수집했습니다 .

function newThing() {
const output = document.getElementsByTagName('output')[0];
if (!(document.forms.thingSelection2.type.value in options)) {
    return false;
}
const list = options[document.forms.thingSelection2.type.value];
const method = document.forms.thingSelection1.mode.value + 'Item';
const item = list[method]();
output.innerHTML = item;

}
document.getElementById('button01').addEventListener('click', newThing);

해결책

이에 대한 나의 해결책은 간단합니다. 다음과 같이 if 문에 줄을 넣으십시오.

if(document.getElementById('button01')) {
    document.getElementById('button01').addEventListener('click', newThing);
}

이렇게하면 사용하지 않는 페이지에서 오류가 제거됩니다.

질문

이로 인해 내가 모르는 버그가 발생할 가능성이 있습니까? 동일한 결과를 얻을 수 있는 더 좋은 방법이 있습니까?

HTML

지금까지이 게시물이받은 피드백에 감사드립니다. 일부 답변에는 HTML 가정을 기반으로 한 권장 사항이 포함되어 있으므로 아래 스 니펫에 각 HTML을 추가하기로 결정했습니다. 한 가지 주목할 점은 두 가지 형식을 사용한다는 것입니다. 이것은 코드의 기능에 필요합니다. 이로 인해 발생할 수있는 의도하지 않은 버그를 알지 못합니다. 그렇다면 아래에 알려주십시오.

<div><output></output></div>
<div><button id="button01">New Thing</button></div>
<div>
   <form name="thingSelection1">
      <input type="radio" name="mode" value="random" id="mode1">&nbsp;Random
      <br/><input type="radio" name="mode" value="forward" id="mode2">&nbsp;Old&nbsp;-&nbsp;New
      <br/><input type="radio" name="mode" value="reverse" id="mode3">&nbsp;New&nbsp;-&nbsp;Old
   </form>
</div>
<div>
   <form name="thingSelection2">
       Doodle&nbsp;<input type="radio" name="type" value="doodle" id="doodleCheck"><br/>
       Video&nbsp;<input type="radio" name="type" value="video" id="videoCheck"><br/>
       Audio&nbsp;<input type="radio" name="type" value="audio" id="audioCheck"><br/>
       Photo&nbsp;<input type="radio" name="type" value="photo" id="photoCheck"><br/>
       Text&nbsp;<input type="radio" name="type" value="text" id="textCheck">
   </form>
 </div>

또한 원본 스 니펫은 전체 JavaScript의 일부일뿐임을 지적하고 싶습니다. 이 게시물 에서 제공 한 정보를 기반으로 나머지 JavaScript를 추가하는 것은 아래 답변 중 일부에 유익하고 관련이있을 수 있지만이 게시물의 원래 주제의 범위를 벗어날 수 있다는 결론을 내 렸습니다. 아래 의견에 이에 대한 귀하의 생각을 적어주십시오.

답변

10 CertainPerformance Oct 21 2020 at 23:58

이로 인해 내가 모르는 버그가 발생할 가능성이 있습니까?

그럴 것 같지 않습니다. if가 청소기 될 수 있지만 문은 괜찮 :

  • 요소를 두 번 선택하는 대신 (한 번은 존재하는지 확인하고 다른 시간에 호출 addEventListener) 변수에 저장합니다.

    const button = document.getElementById('button01');
    if (button) {
      button.addEventListener('click', newThing);
    }
    
  • 또는 프로덕션을 위해 트랜스 파일되는 JS를 작성하는 경우 (전문적이거나 더 큰 프로젝트에서는 실제로 그래야 함) 선택적 체인을 사용합니다.

    document.getElementById('button01')?.addEventListener('click', newThing);
    

그러나 근본적인 문제는 남아 있습니다. HTML 레이아웃은 JavaScript에서 완전히 분리되어 있습니다. 처음에 버튼이 있는지 확인해야하는 것은 적어도 더 크거나 더 전문적인 프로젝트에서 이상적으로는 문제가되지 않아야하는 문제 입니다. 1 개가 아니라 3 개 또는 5 개 또는 다른 페이지에 핸들러가있는 10 개 요소가있는 경우, 모두 존재하거나 존재하지 않을 수 있습니다. 코드베이스는 유지하기가 더 어려울 것입니다.

이에 대한 몇 가지 해결책이 있습니다.

  • 한 가지 옵션은 다음 <script>과 같은 형식의 페이지에 대해 별도의 파일을 만드는 것입니다 .
<form id="thingSelection2">
...
</form>
<script src="./thingSelection.js"></script>

여기서 thingSelection.js이벤트 리스너를 추가합니다. 그러나 서버에 대한 별도의 요청이 필요합니다. 이는 HTTP 1.1의 큰 페이지에서 문제가 될 수 있습니다. 이와 같은 다른 스크립트가 많으면 병렬 요청 수가 많을수록 속도가 느려질 수 있습니다. 높은 대기 시간. (HTTP / 2 프로토콜은 동일한 서버 IIRC에 대한 추가 연결에 문제가 없습니다.)

(스크립트를 인라인 할 수도 </form><script>// etc</script>있지만 , 캐싱을 허용하기 위해 스크립트를 별도의 파일에 두는 것을 선호합니다)

  • 하지만 저라면 HTML 생성을 해당 HTML에 대한 이벤트 리스너와 완전히 통합하여 프레임 워크를 사용하여 다른 하나없이 존재하는 것을 완전히 상상할 수 없게 만드는 것을 강력히 선호합니다. 예를 들어 React를 사용하면 다음과 같은 작업을 수행 할 수 있습니다.
const Things = () => {
    const [item, setItem] = useState('');
    const [selectedOption, setSelectedOption] = useState('foo');
    const clickHandler = (e) => {
        const fn = options[selectedOption];
        if (fn) {
          setItem(fn());
        }
    };
    return (
        <div>
            <select value={selectedOption} onChange={e => setSelectedOption(e.currentTarget.value)}>
                <option value="foo">foo</option>
                <option value="bar">bar</option>
            </select>
            <button onClick={clickHandler}>click</button>
            <output>{item}</output>
        </div>
    );
};

const options = {
  foo: () => 'foo',
  bar: () => 'bar',
};

const Things = () => {
    const [item, setItem] = React.useState('');
    const [selectedOption, setSelectedOption] = React.useState('foo');
    const clickHandler = (e) => {
        const fn = options[selectedOption];
        if (fn) {
          setItem(fn());
        }
    };
    return (
        <div>
            <select value={selectedOption} onChange={e => setSelectedOption(e.currentTarget.value)}>
                <option value="foo">foo</option>
                <option value="bar">bar</option>
            </select>
            <button onClick={clickHandler}>click</button>
            <output>{item}</output>
        </div>
    );
};
ReactDOM.render(<Things />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class="react"></div>

그런 다음 Things가 필요한 페이지에있을 때 <Things />. 이 접근 방식을 사용하면 HTML과 관련된 JS 핸들러가 모두 Things. 의도하지 않은 충돌이있을 수있는 선택기를 사용할 필요가 없습니다. (예를 들어, 당신의 원본 코드와 함께가는 document.getElementById('button01')경우 어떻게, 다른 페이지 중 하나에있는 HTML의 완전히 분리 된 섹션 실수로 동일한 ID를 사용하는 요소를했다? 그럼 당신은 문제가있을 수 있습니다.)

이와 같은 프레임 워크를 사용하면 배울 것이 많지만 코드베이스를 훨씬 더 유지 관리 할 수 ​​있습니다. 중간 규모 이상의 프로젝트 인 IMO에는 그만한 가치가 있습니다.


다른 메모에서 현재 코드도 약간 개선 될 수 있습니다.

선택자 선호 선택자 문자열은 일반적으로 요소를 선택하는 다른 방법 ( getElementsByTagNamedocument.forms.someFormName.someFormElement) 보다 더 쉽게 이해되고 유연 합니다. 요소와 일치하는 선택기 문자열은 요소의 스타일을 지정하는 CSS 선택기와 정렬됩니다. 예를 들어 다음을 교체합니다.

document.forms.thingSelection2.type.value

document.querySelector('#thingSelection2 [name=type]').value

const output = document.getElementsByTagName('output')[0];

const output = document.querySelector('output');

(하나의 일치하는 요소 만 필요한 경우 컬렉션을 선택할 필요 없음)

값을 저장하십시오. 값을 두 번 선택하고 추출하는 대신 DRY 코드를 작성하십시오. 변수에 넣으십시오.

const { value } = document.querySelector('#thingSelection2 [name=type]');
if (value in options) { // Or, use `!options[value]
  return false;
}
4 SᴀᴍOnᴇᴌᴀ Oct 22 2020 at 05:20

귀하의 다른 게시물 중 하나에 대한 제 답변의 조언 을 통합 한 것 같습니다 .

SomePerformance의 답변에는 훌륭한 조언이 있습니다. 그것의 본질과 함께 간다 "그 코드의 다른 부분이 있습니다 수있는 문제가 될 수는"

const output = document.getElementsByTagName('output')[0];

HTML 코드에 요소 있는 경우 <output>요소 가 있어야 하지만 , DOM이 준비되기 전에 JavaScript가 실행되면 id 속성으로 요소를 가져 오는 코드와 마찬가지로 오류가 발생할 수 있습니다 .

const output = document.getElementsByTagName('output')[0];
console.log('output tagName:', output.innerHTML)

document.getElementsByTagName('output')첫 번째 요소에 액세스하기 전에 길이가 0이 아닌지 확인하는 것이 가장 좋습니다 .


또 다른 제안은 가능한 한 빨리 반환하는 것입니다. 예를 들어 할당 output할 줄은 조건에 영향을주지 않습니다.

const output = document.getElementsByTagName('output')[0];
if (!(document.forms.thingSelection2.type.value in options)) {
    return false;
}

출력 요소가 DOM에서 가져 오는지 여부는이 코드에 차이가 없지만 계산을 줄이는 것은 개발하기에 좋은 습관입니다. 다른 상황에서는 사용자에게 귀중한 시간을 절약 할 수 있습니다. 예를 들어 데이터를 가져 오는 서버 측 요청의 예를 들어 보면 몇 초가 걸릴 수 있습니다.

3 Dan Oct 22 2020 at 18:57

HTML이 다음과 같이 보인다고 가정합니다.

<output></output>
<output></output>
<form>
  <input name="mode" value="..." />
  <input name="type" value="..." />
  <button>Click!</button>
</form>

내 핵심 권장 사항은 페이지별로 고유 한 ID에 의존하지 않는 것입니다. 대신 데이터 속성에 의존하고 특정 개수가 있다고 가정하지 마십시오. 자바 스크립트는 가능한 한 HTML 레이아웃에서 분리 되어야 합니다.

가지고있는 것을 조금 수정 해 봅시다. 특수 버튼이 있음을 JavaScript에 나타내는 데이터 속성을 추가하는 것으로 시작하겠습니다.

<button data-my-widget>Click!</button>

이렇게하면 주어진 속성을 가진 모든 버튼에 이벤트 리스너를 할당 할 수 있고 버튼이 점진적으로 향상 될 수 있습니다.

const myThing = event => {
  ...
}

for (const button of document.querySelectorAll('[data-my-widget]')) {
  button.addEventListener('click', newThing);
}

또한 이벤트 처리기 내에서 각 단추 및 각 컨트롤에 적용되는 양식을 하드 코딩합니다. 에서 양식 속성을 사용 button하고 대상 요소 ID를 데이터 속성으로 제공 하여이 작업을 좀 더 개선 할 수 있습니다 . :

<form>
  <input name="mode" value="...." />
  <input name="type" value="...." />
  <button data-my-widget data-mode="mode" data-value="type">Click!</button>
</form>

그런 다음 핸들러 내에서이 양식에 액세스 할 수 있습니다.

const myThing = event => {
  const outputs = document.getElementsByTagName('output');
  if (outputs.length === 0) {
    return;
  }

  const output = outputs[0];
  const target = event.target;
  const { form } = target;
  if (form === undefined) {
    return;
  }

  const { mode: modeId, value: valueId } = target.dataset;
  const mode = form.elements[modeId]?.value;
  const value = form.elements[valueId]?.value;
  if (mode === undefined || value === undefined || value in options === false) {
    return;
  }

  const list = options[value];
  const f = list[`${mode}Item`];
  if (f === undefined) {
    return;
  }
  output.innerHTML = f();
}

이렇게하면 자바 스크립트가 가능한 한 HTML 구조를 인식하지 못하지만 outputs요소의 색인에 의존하지 않고 잠재적으로 데이터 속성을 사용하여 섹션을 교체하는 것이 좋습니다 .

<output id="output-1"></output> 
<button ... data-output="output-1">Click!</button>

const myThing = event => {
  ...
  const { mode: modeId, value: valueId, output: outputId } = target.dataset;
  ...
  const output = document.getElementById(outputId);
  output.innerHTML = ...;
}

이 모든 것을 합치면 :

<output id="output-1"></output>
...
<output id="output-n"></output>
<form>
  <input name="mode" value="...." />
  <input name="type" value="...." />
  <button data-my-widget data-mode="mode" data-value="type" data-output="output-1">Click!</button>
</form>

const handleClick = event => {
  const { form } = event.target;
  if (form === undefined) {
    return;
  }

  const { mode: modeId, value: valueId, output: outputId } = target.dataset;
  const mode = form.elements[modeId]?.value;
  const value = form.elements[valueId]?.value;
  const output = document.getElementById(outputId);
  if (mode === undefined || value === undefined || output === undefined) {
    return;
  }

  const list = options[value];
  const f = list[`${mode}Item`];
  if (f === undefined) {
    return;
  }

  output.innerHTML = f();
}

for (const button of document.querySelectorAll('[data-my-widget]')) {
  button.addEventListener('click', handleClick);
}